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

daisytuner / docc / 25459771247

06 May 2026 08:37PM UTC coverage: 65.659% (+0.4%) from 65.223%
25459771247

push

github

web-flow
Merge pull request #704 from daisytuner/lcd-analysis

Refactors LoopCarriedDependencyAnalysis into standalone analysis

731 of 828 new or added lines in 13 files covered. (88.29%)

42 existing lines in 5 files now uncovered.

32175 of 49003 relevant lines covered (65.66%)

12934.86 hits per line

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

92.37
/sdfg/src/analysis/data_dependency_analysis.cpp
1
#include "sdfg/analysis/data_dependency_analysis.h"
2

3
#include <cassert>
4
#include <string>
5
#include <unordered_map>
6
#include <unordered_set>
7
#include <vector>
8

9
#include <isl/ctx.h>
10
#include <isl/map.h>
11
#include <isl/options.h>
12
#include <isl/set.h>
13

14
#include "sdfg/analysis/analysis.h"
15
#include "sdfg/analysis/loop_analysis.h"
16
#include "sdfg/structured_sdfg.h"
17
#include "sdfg/symbolic/maps.h"
18
#include "sdfg/symbolic/sets.h"
19

20
namespace sdfg {
21
namespace analysis {
22

23
DataDependencyAnalysis::DataDependencyAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg), node_(sdfg.root()) {};
158✔
24

25
DataDependencyAnalysis::DataDependencyAnalysis(StructuredSDFG& sdfg, structured_control_flow::Sequence& node)
26
    : Analysis(sdfg), node_(node) {};
166✔
27

28
void DataDependencyAnalysis::run(analysis::AnalysisManager& analysis_manager) {
299✔
29
    results_.clear();
299✔
30
    undefined_users_.clear();
299✔
31
    loop_boundaries_.clear();
299✔
32

33
    std::unordered_set<User*> undefined;
299✔
34
    std::unordered_map<User*, std::unordered_set<User*>> open_definitions;
299✔
35
    std::unordered_map<User*, std::unordered_set<User*>> closed_definitions;
299✔
36

37
    visit_sequence(analysis_manager, node_, undefined, open_definitions, closed_definitions);
299✔
38

39
    for (auto& entry : open_definitions) {
2,787✔
40
        closed_definitions.insert(entry);
2,787✔
41
    }
2,787✔
42

43
    for (auto& entry : closed_definitions) {
2,882✔
44
        if (results_.find(entry.first->container()) == results_.end()) {
2,882✔
45
            results_.insert({entry.first->container(), {}});
1,609✔
46
        }
1,609✔
47
        results_.at(entry.first->container()).insert(entry);
2,882✔
48
    }
2,882✔
49
};
299✔
50

51
/****** Visitor API ******/
52

53
void DataDependencyAnalysis::visit_block(
54
    analysis::AnalysisManager& analysis_manager,
55
    structured_control_flow::Block& block,
56
    std::unordered_set<User*>& undefined,
57
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
58
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
59
) {
978✔
60
    auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
978✔
61
    auto& users = analysis_manager.get<analysis::Users>();
978✔
62

63
    auto& dataflow = block.dataflow();
978✔
64

65
    for (auto node : dataflow.topological_sort()) {
3,275✔
66
        if (dynamic_cast<data_flow::ConstantNode*>(node) != nullptr) {
3,275✔
67
            continue;
198✔
68
        }
198✔
69

70
        if (auto access_node = dynamic_cast<data_flow::AccessNode*>(node)) {
3,077✔
71
            if (!symbolic::is_pointer(symbolic::symbol(access_node->data()))) {
2,154✔
72
                if (dataflow.in_degree(*node) > 0) {
2,154✔
73
                    Use use = Use::WRITE;
924✔
74
                    for (auto& iedge : dataflow.in_edges(*access_node)) {
924✔
75
                        if (iedge.type() == data_flow::MemletType::Reference ||
924✔
76
                            iedge.type() == data_flow::MemletType::Dereference_Src) {
924✔
77
                            use = Use::MOVE;
2✔
78
                            break;
2✔
79
                        }
2✔
80
                    }
924✔
81

82
                    if (use == Use::WRITE) {
924✔
83
                        auto current_user = users.get_user(access_node->data(), access_node, use);
922✔
84

85
                        // Close open definitions if possible
86
                        std::unordered_map<User*, std::unordered_set<User*>> to_close;
922✔
87
                        for (auto& user : open_definitions) {
922✔
88
                            if (this->closes(analysis_manager, *user.first, *current_user, false)) {
792✔
89
                                to_close.insert(user);
43✔
90
                            }
43✔
91
                        }
792✔
92
                        for (auto& user : to_close) {
922✔
93
                            open_definitions.erase(user.first);
43✔
94
                            closed_definitions.insert(user);
43✔
95
                        }
43✔
96

97
                        // Start new open definition
98
                        open_definitions.insert({current_user, {}});
922✔
99
                    }
922✔
100
                }
924✔
101
                if (dataflow.out_degree(*access_node) > 0) {
2,154✔
102
                    Use use = Use::READ;
1,294✔
103
                    for (auto& oedge : dataflow.out_edges(*access_node)) {
1,398✔
104
                        if (oedge.type() == data_flow::MemletType::Reference ||
1,398✔
105
                            oedge.type() == data_flow::MemletType::Dereference_Dst) {
1,398✔
106
                            use = Use::VIEW;
2✔
107
                            break;
2✔
108
                        }
2✔
109
                    }
1,398✔
110

111
                    if (use == Use::READ) {
1,294✔
112
                        auto current_user = users.get_user(access_node->data(), access_node, use);
1,292✔
113

114
                        // Assign to open definitions
115
                        bool found_user = false;
1,292✔
116
                        bool found_undefined_user = false;
1,292✔
117
                        for (auto& user : open_definitions) {
1,292✔
118
                            if (this->depends(analysis_manager, *user.first, *current_user)) {
1,201✔
119
                                user.second.insert(current_user);
357✔
120
                                found_user = true;
357✔
121
                                found_undefined_user = this->is_undefined_user(*user.first);
357✔
122
                            }
357✔
123
                        }
1,201✔
124
                        // If no definition found, undefined user found, or
125
                        // the read's subset footprint is only partially
126
                        // covered by the matching open writers, mark as
127
                        // upward-exposed (undefined). `depends` uses
128
                        // intersect-any across the cartesian product of
129
                        // subsets, which silently swallows partial-cover
130
                        // multi-subset reads (e.g. an access node with two
131
                        // out-edges where only one edge is killed inside
132
                        // the current scope).
133
                        if (!found_user || found_undefined_user ||
1,292✔
134
                            !this->fully_covered(analysis_manager, *current_user, open_definitions)) {
1,292✔
135
                            undefined.insert(current_user);
991✔
136
                        }
991✔
137
                    }
1,292✔
138
                }
1,294✔
139
            }
2,154✔
140
        } else if (auto library_node = dynamic_cast<data_flow::LibraryNode*>(node)) {
2,154✔
141
            for (auto& symbol : library_node->symbols()) {
15✔
142
                auto current_user = users.get_user(symbol->get_name(), library_node, Use::READ);
11✔
143

144
                // Assign to open definitions
145
                bool found_user = false;
11✔
146
                bool found_undefined_user = false;
11✔
147
                for (auto& user : open_definitions) {
11✔
148
                    if (this->depends(analysis_manager, *user.first, *current_user)) {
5✔
149
                        user.second.insert(current_user);
×
150
                        found_user = true;
×
151
                        found_undefined_user = this->is_undefined_user(*current_user);
×
152
                    }
×
153
                }
5✔
154
                // If no definition found or undefined user found, mark as undefined
155
                if (!found_user || found_undefined_user) {
11✔
156
                    undefined.insert(current_user);
11✔
157
                }
11✔
158
            }
11✔
159
        }
15✔
160

161
        for (auto& oedge : dataflow.out_edges(*node)) {
3,077✔
162
            std::unordered_set<std::string> used;
2,319✔
163
            for (auto& dim : oedge.subset()) {
2,695✔
164
                for (auto atom : symbolic::atoms(dim)) {
2,973✔
165
                    used.insert(atom->get_name());
2,973✔
166
                }
2,973✔
167
            }
2,695✔
168
            for (auto& atom : used) {
2,924✔
169
                auto current_user = users.get_user(atom, &oedge, Use::READ);
2,924✔
170

171
                // Assign to open definitions
172
                bool found_user = false;
2,924✔
173
                bool found_undefined_user = false;
2,924✔
174
                for (auto& user : open_definitions) {
2,924✔
175
                    if (this->depends(analysis_manager, *user.first, *current_user)) {
2,426✔
176
                        user.second.insert(current_user);
19✔
177
                        found_user = true;
19✔
178
                        found_undefined_user = this->is_undefined_user(*user.first);
19✔
179
                    }
19✔
180
                }
2,426✔
181
                // If no definition found or undefined user found, mark as undefined
182
                if (!found_user || found_undefined_user) {
2,924✔
183
                    undefined.insert(current_user);
2,905✔
184
                }
2,905✔
185
            }
2,924✔
186
        }
2,319✔
187
    }
3,077✔
188
}
978✔
189

190
void DataDependencyAnalysis::visit_for(
191
    analysis::AnalysisManager& analysis_manager,
192
    structured_control_flow::StructuredLoop& for_loop,
193
    std::unordered_set<User*>& undefined,
194
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
195
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
196
) {
946✔
197
    auto& users = analysis_manager.get<analysis::Users>();
946✔
198

199
    // Init - Read
200
    for (auto atom : symbolic::atoms(for_loop.init())) {
946✔
201
        auto current_user = users.get_user(atom->get_name(), &for_loop, Use::READ, true);
174✔
202

203
        // Assign to open definitions
204
        bool found_user = false;
174✔
205
        bool found_undefined_user = false;
174✔
206
        for (auto& user : open_definitions) {
344✔
207
            if (this->depends(analysis_manager, *user.first, *current_user)) {
344✔
208
                user.second.insert(current_user);
3✔
209
                found_user = true;
3✔
210
                found_undefined_user = this->is_undefined_user(*user.first);
3✔
211
            }
3✔
212
        }
344✔
213
        // If no definition found or undefined user found, mark as undefined
214
        if (!found_user || found_undefined_user) {
174✔
215
            undefined.insert(current_user);
172✔
216
        }
172✔
217
    }
174✔
218

219
    // Init - Write
220
    {
946✔
221
        // Write Induction Variable
222
        auto current_user = users.get_user(for_loop.indvar()->get_name(), &for_loop, Use::WRITE, true);
946✔
223

224
        // Close open definitions if possible
225
        std::unordered_map<User*, std::unordered_set<User*>> to_close;
946✔
226
        for (auto& user : open_definitions) {
1,498✔
227
            if (this->closes(analysis_manager, *user.first, *current_user, true)) {
1,498✔
228
                to_close.insert(user);
2✔
229
            }
2✔
230
        }
1,498✔
231
        for (auto& user : to_close) {
946✔
232
            open_definitions.erase(user.first);
2✔
233
            closed_definitions.insert(user);
2✔
234
        }
2✔
235

236
        // Start new open definition
237
        open_definitions.insert({current_user, {}});
946✔
238
    }
946✔
239

240
    // Update - Write
241
    {
946✔
242
        auto current_user = users.get_user(for_loop.indvar()->get_name(), &for_loop, Use::WRITE, false, false, true);
946✔
243
        open_definitions.insert({current_user, {}});
946✔
244
    }
946✔
245

246
    // Condition - Read
247
    for (auto atom : symbolic::atoms(for_loop.condition())) {
1,945✔
248
        auto current_user = users.get_user(atom->get_name(), &for_loop, Use::READ, false, true);
1,945✔
249

250
        // Assign to open definitions
251
        bool found_user = false;
1,945✔
252
        bool found_undefined_user = false;
1,945✔
253
        for (auto& user : open_definitions) {
7,026✔
254
            if (this->depends(analysis_manager, *user.first, *current_user)) {
7,026✔
255
                user.second.insert(current_user);
1,895✔
256
                found_user = true;
1,895✔
257
                found_undefined_user = this->is_undefined_user(*user.first);
1,895✔
258
            }
1,895✔
259
        }
7,026✔
260
        // If no definition found or undefined user found, mark as undefined
261
        if (!found_user || found_undefined_user) {
1,945✔
262
            undefined.insert(current_user);
997✔
263
        }
997✔
264
    }
1,945✔
265

266
    std::unordered_map<User*, std::unordered_set<User*>> open_definitions_for;
946✔
267
    std::unordered_map<User*, std::unordered_set<User*>> closed_definitions_for;
946✔
268
    std::unordered_set<User*> undefined_for;
946✔
269

270
    // Add assumptions for body
271
    visit_sequence(analysis_manager, for_loop.root(), undefined_for, open_definitions_for, closed_definitions_for);
946✔
272

273
    // Update - Read
274
    for (auto atom : symbolic::atoms(for_loop.update())) {
946✔
275
        auto current_user = users.get_user(atom->get_name(), &for_loop, Use::READ, false, false, true);
946✔
276

277
        // Assign to open definitions
278
        bool found_user = false;
946✔
279
        bool found_undefined_user = false;
946✔
280
        for (auto& user : open_definitions_for) {
3,992✔
281
            if (this->depends(analysis_manager, *user.first, *current_user)) {
3,992✔
282
                user.second.insert(current_user);
1✔
283
                found_user = true;
1✔
284
                found_undefined_user = this->is_undefined_user(*user.first);
1✔
285
            }
1✔
286
        }
3,992✔
287
        // If no definition found or undefined user found, mark as undefined
288
        if (!found_user || found_undefined_user) {
946✔
289
            undefined_for.insert(current_user);
945✔
290
        }
945✔
291
    }
946✔
292

293
    // Merge for with outside
294
    auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
946✔
295

296
    // Closed definitions are simply merged
297
    for (auto& entry : closed_definitions_for) {
946✔
298
        closed_definitions.insert(entry);
70✔
299
    }
70✔
300

301
    // Undefined reads are matched or forwarded
302
    for (auto open_read : undefined_for) {
10,123✔
303
        // Simple check: no match or undefined user
304
        std::unordered_set<User*> frontier;
10,123✔
305
        bool found = false;
10,123✔
306
        bool found_undefined_user = false;
10,123✔
307
        for (auto& entry : open_definitions) {
34,917✔
308
            if (intersects(*entry.first, *open_read, analysis_manager)) {
34,917✔
309
                entry.second.insert(open_read);
8,468✔
310
                found = true;
8,468✔
311
                found_undefined_user = this->is_undefined_user(*entry.first);
8,468✔
312
                frontier.insert(entry.first);
8,468✔
313
            }
8,468✔
314
        }
34,917✔
315
        if (!found || found_undefined_user) {
10,123✔
316
            undefined.insert(open_read);
5,767✔
317
            continue;
5,767✔
318
        }
5,767✔
319

320
        // Users found, check if they fully cover the read
321
        bool covered = false;
4,356✔
322
        for (auto& entry : frontier) {
4,771✔
323
            if (!dominance_analysis.dominates(*entry, *open_read)) {
4,771✔
324
                continue;
650✔
325
            }
650✔
326
            bool covers = supersedes_restrictive(*open_read, *entry, analysis_manager);
4,121✔
327
            if (covers) {
4,121✔
328
                covered = true;
4,097✔
329
                break;
4,097✔
330
            }
4,097✔
331
        }
4,121✔
332
        if (!covered) {
4,356✔
333
            undefined.insert(open_read);
259✔
334
        }
259✔
335
    }
4,356✔
336

337
    // Open definitions may close outside open definitions after loop
338
    std::unordered_set<User*> to_close;
946✔
339
    for (auto& previous : open_definitions) {
3,388✔
340
        for (auto& user : open_definitions_for) {
13,349✔
341
            if (this->closes(analysis_manager, *previous.first, *user.first, true)) {
13,349✔
342
                to_close.insert(previous.first);
×
343
                break;
×
344
            }
×
345
        }
13,349✔
346
    }
3,388✔
347
    for (auto& user : to_close) {
946✔
348
        closed_definitions.insert({user, open_definitions.at(user)});
×
349
        open_definitions.erase(user);
×
350
    }
×
351

352
    // Cross-iteration linkage for SCALARS only.
353
    //
354
    // An in-body upward-exposed read may be satisfied (in some non-first
355
    // iteration) by an in-body open writer. We must record those
356
    // (write -> read) edges in `open_definitions_for` so that public
357
    // queries like `defined_by(read)` return the in-body writer in
358
    // addition to any pre-loop write -- otherwise consumers (e.g.
359
    // SymbolPropagation) treat the read as having a single dominating
360
    // definition and incorrectly fold the loop-carried value away.
361
    //
362
    // We restrict to scalar containers because:
363
    //   - For scalars, every access touches the same cell, so the
364
    //     prior-iteration writer always reaches the in-body read --
365
    //     this is sound and precise.
366
    //   - For arrays, deciding whether a writer flows across iterations
367
    //     requires precise per-pair delta analysis (done in LCDA).
368
    //     Adding edges via the over-approximate `depends` predicate
369
    //     would create spurious in-body RAW links that pollute
370
    //     consumers reasoning about precise dataflow on arrays.
371
    //
372
    // This must run BEFORE the snapshot below and BEFORE merging into the
373
    // outer `open_definitions`, since both perform by-value copies of the
374
    // (writer -> readers) sets we are mutating.
375
    for (auto* open_read : undefined_for) {
10,123✔
376
        auto& type = this->sdfg_.type(open_read->container());
10,123✔
377
        if (!dynamic_cast<const types::Scalar*>(&type)) continue;
10,123✔
378
        for (auto& write_entry : open_definitions_for) {
45,679✔
379
            if (write_entry.first->container() != open_read->container()) continue;
45,679✔
380
            if (this->is_undefined_user(*write_entry.first)) continue;
13✔
381
            write_entry.second.insert(open_read);
12✔
382
        }
12✔
383
    }
7,771✔
384

385
    // Snapshot loop boundary sets so LoopCarriedDependencyAnalysis can compute LCDs.
386
    loop_boundaries_[&for_loop] = std::make_pair(undefined_for, open_definitions_for);
946✔
387

388
    // Add open definitions from for to outside
389
    for (auto& entry : open_definitions_for) {
3,992✔
390
        open_definitions.insert(entry);
3,992✔
391
    }
3,992✔
392
}
946✔
393

394
void DataDependencyAnalysis::visit_if_else(
395
    analysis::AnalysisManager& analysis_manager,
396
    structured_control_flow::IfElse& if_else,
397
    std::unordered_set<User*>& undefined,
398
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
399
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
400
) {
31✔
401
    auto& users = analysis_manager.get<analysis::Users>();
31✔
402

403
    // Read Conditions
404
    for (size_t i = 0; i < if_else.size(); i++) {
86✔
405
        auto child = if_else.at(i).second;
55✔
406
        for (auto atom : symbolic::atoms(child)) {
55✔
407
            auto current_user = users.get_user(atom->get_name(), &if_else, Use::READ);
48✔
408

409
            bool found_user = false;
48✔
410
            bool found_undefined_user = false;
48✔
411
            for (auto& user : open_definitions) {
48✔
412
                if (this->depends(analysis_manager, *user.first, *current_user)) {
20✔
413
                    user.second.insert(current_user);
9✔
414
                    found_user = true;
9✔
415
                    found_undefined_user = this->is_undefined_user(*user.first);
9✔
416
                }
9✔
417
            }
20✔
418
            // If no definition found or undefined user found, mark as undefined
419
            if (!found_user || found_undefined_user) {
48✔
420
                undefined.insert(current_user);
39✔
421
            }
39✔
422
        }
48✔
423
    }
55✔
424

425
    std::vector<std::unordered_set<User*>> undefined_branches(if_else.size());
31✔
426
    std::vector<std::unordered_map<User*, std::unordered_set<User*>>> open_definitions_branches(if_else.size());
31✔
427
    std::vector<std::unordered_map<User*, std::unordered_set<User*>>> closed_definitionss_branches(if_else.size());
31✔
428
    for (size_t i = 0; i < if_else.size(); i++) {
86✔
429
        auto& child = if_else.at(i).first;
55✔
430
        visit_sequence(
55✔
431
            analysis_manager,
55✔
432
            child,
55✔
433
            undefined_branches.at(i),
55✔
434
            open_definitions_branches.at(i),
55✔
435
            closed_definitionss_branches.at(i)
55✔
436
        );
55✔
437
    }
55✔
438

439
    // merge partial open reads
440
    for (size_t i = 0; i < if_else.size(); i++) {
86✔
441
        for (auto& entry : undefined_branches.at(i)) {
55✔
442
            bool found_user = false;
35✔
443
            bool found_undefined_user = false;
35✔
444
            for (auto& user : open_definitions) {
35✔
445
                if (this->depends(analysis_manager, *user.first, *entry)) {
12✔
446
                    user.second.insert(entry);
6✔
447
                    found_user = true;
6✔
448
                    found_undefined_user = this->is_undefined_user(*user.first);
6✔
449
                }
6✔
450
            }
12✔
451
            // If no definition found or undefined user found, mark as undefined
452
            if (!found_user || found_undefined_user) {
35✔
453
                undefined.insert(entry);
29✔
454
            }
29✔
455
        }
35✔
456
    }
55✔
457

458
    // merge closed writes
459
    for (auto& closing : closed_definitionss_branches) {
55✔
460
        for (auto& entry : closing) {
55✔
461
            closed_definitions.insert(entry);
×
462
        }
×
463
    }
55✔
464

465
    // Close open reads_after_writes for complete branches
466
    if (if_else.is_complete()) {
31✔
467
        std::unordered_map<User*, std::unordered_set<User*>> to_close;
24✔
468
        std::unordered_set<std::string> candidates;
24✔
469
        std::unordered_set<std::string> candidates_tmp;
24✔
470

471
        /* Complete close open reads_after_writes
472
        1. get candidates from first iteration
473
        2. iterate over all branches and prune candidates
474
        3. find prior writes for remaining candidates
475
        4. close open reads_after_writes for all candidates
476
        */
477
        for (auto& entry : open_definitions_branches.at(0)) {
31✔
478
            candidates.insert(entry.first->container());
31✔
479
        }
31✔
480
        for (auto& entry : closed_definitionss_branches.at(0)) {
24✔
481
            candidates.insert(entry.first->container());
×
482
        }
×
483

484
        for (size_t i = 1; i < if_else.size(); i++) {
48✔
485
            for (auto& entry : open_definitions_branches.at(i)) {
24✔
486
                if (candidates.find(entry.first->container()) != candidates.end()) {
22✔
487
                    candidates_tmp.insert(entry.first->container());
21✔
488
                }
21✔
489
            }
22✔
490
            candidates.swap(candidates_tmp);
24✔
491
            candidates_tmp.clear();
24✔
492
        }
24✔
493

494
        for (auto& entry : open_definitions) {
24✔
495
            if (candidates.find(entry.first->container()) != candidates.end()) {
9✔
496
                to_close.insert(entry);
4✔
497
            }
4✔
498
        }
9✔
499

500
        for (auto& entry : to_close) {
24✔
501
            open_definitions.erase(entry.first);
4✔
502
            closed_definitions.insert(entry);
4✔
503
        }
4✔
504
    } else {
24✔
505
        // Incomplete if-else
506

507
        // In order to determine whether a new read is undefined
508
        // we would need to check whether all open definitions
509
        // jointly dominate the read.
510
        // Since this is expensive, we apply a trick:
511
        // For incomplete if-elses and any newly opened definition in
512
        // any branch, we add an artificial undefined user for that container.
513
        // If we encounter this user later, we know that not all branches defined it.
514
        // Hence, we can mark the read as (partially) undefined.
515

516
        for (auto& branch : open_definitions_branches) {
7✔
517
            for (auto& open_definition : branch) {
7✔
518
                auto write = open_definition.first;
5✔
519
                auto artificial_user = std::make_unique<
5✔
520
                    User>(boost::graph_traits<graph::Graph>::null_vertex(), write->container(), nullptr, Use::WRITE);
5✔
521
                this->undefined_users_.push_back(std::move(artificial_user));
5✔
522
                open_definitions.insert({this->undefined_users_.back().get(), {}});
5✔
523
            }
5✔
524
        }
7✔
525
    }
7✔
526

527
    // Add open definitions from branches to outside
528
    for (auto& branch : open_definitions_branches) {
55✔
529
        for (auto& entry : branch) {
58✔
530
            open_definitions.insert(entry);
58✔
531
        }
58✔
532
    }
55✔
533
}
31✔
534

535
void DataDependencyAnalysis::visit_while(
536
    analysis::AnalysisManager& analysis_manager,
537
    structured_control_flow::While& while_loop,
538
    std::unordered_set<User*>& undefined,
539
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
540
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
541
) {
13✔
542
    std::unordered_map<User*, std::unordered_set<User*>> open_definitions_while;
13✔
543
    std::unordered_map<User*, std::unordered_set<User*>> closed_definitions_while;
13✔
544
    std::unordered_set<User*> undefined_while;
13✔
545

546
    visit_sequence(analysis_manager, while_loop.root(), undefined_while, open_definitions_while, closed_definitions_while);
13✔
547

548
    // Scope-local closed definitions
549
    for (auto& entry : closed_definitions_while) {
13✔
550
        closed_definitions.insert(entry);
2✔
551
    }
2✔
552

553
    for (auto open_read : undefined_while) {
24✔
554
        // Over-Approximation: Add loop-carried dependencies for all open reads
555
        for (auto& entry : open_definitions_while) {
117✔
556
            if (entry.first->container() == open_read->container()) {
117✔
557
                entry.second.insert(open_read);
13✔
558
            }
13✔
559
        }
117✔
560

561
        // Connect to outside
562
        bool found = false;
24✔
563
        for (auto& entry : open_definitions) {
24✔
564
            if (entry.first->container() == open_read->container()) {
3✔
565
                entry.second.insert(open_read);
2✔
566
                found = true;
2✔
567
            }
2✔
568
        }
3✔
569
        if (!found) {
24✔
570
            undefined.insert(open_read);
22✔
571
        }
22✔
572
    }
24✔
573

574
    // Add open definitions from while to outside
575
    for (auto& entry : open_definitions_while) {
40✔
576
        open_definitions.insert(entry);
40✔
577
    }
40✔
578
}
13✔
579

580
void DataDependencyAnalysis::visit_return(
581
    analysis::AnalysisManager& analysis_manager,
582
    structured_control_flow::Return& return_statement,
583
    std::unordered_set<User*>& undefined,
584
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
585
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
586
) {
8✔
587
    auto& users = analysis_manager.get<analysis::Users>();
8✔
588

589
    if (return_statement.is_data() && !return_statement.data().empty()) {
8✔
590
        auto current_user = users.get_user(return_statement.data(), &return_statement, Use::READ);
8✔
591

592
        bool found = false;
8✔
593
        for (auto& user : open_definitions) {
41✔
594
            if (user.first->container() == return_statement.data()) {
41✔
595
                user.second.insert(current_user);
14✔
596
                found = true;
14✔
597
            }
14✔
598
        }
41✔
599
        if (!found) {
8✔
600
            undefined.insert(current_user);
1✔
601
        }
1✔
602
    }
8✔
603

604
    // close all open reads_after_writes
605
    for (auto& entry : open_definitions) {
41✔
606
        closed_definitions.insert(entry);
41✔
607
    }
41✔
608
    open_definitions.clear();
8✔
609
}
8✔
610

611
void DataDependencyAnalysis::visit_sequence(
612
    analysis::AnalysisManager& analysis_manager,
613
    structured_control_flow::Sequence& sequence,
614
    std::unordered_set<User*>& undefined,
615
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
616
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
617
) {
1,330✔
618
    auto& users = analysis_manager.get<analysis::Users>();
1,330✔
619

620
    for (size_t i = 0; i < sequence.size(); i++) {
3,306✔
621
        auto child = sequence.at(i);
1,976✔
622
        if (auto block = dynamic_cast<structured_control_flow::Block*>(&child.first)) {
1,976✔
623
            visit_block(analysis_manager, *block, undefined, open_definitions, closed_definitions);
970✔
624
        } else if (auto for_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child.first)) {
1,006✔
625
            visit_for(analysis_manager, *for_loop, undefined, open_definitions, closed_definitions);
946✔
626
        } else if (auto if_else = dynamic_cast<structured_control_flow::IfElse*>(&child.first)) {
946✔
627
            visit_if_else(analysis_manager, *if_else, undefined, open_definitions, closed_definitions);
31✔
628
        } else if (auto while_loop = dynamic_cast<structured_control_flow::While*>(&child.first)) {
31✔
629
            visit_while(analysis_manager, *while_loop, undefined, open_definitions, closed_definitions);
13✔
630
        } else if (auto return_statement = dynamic_cast<structured_control_flow::Return*>(&child.first)) {
16✔
631
            visit_return(analysis_manager, *return_statement, undefined, open_definitions, closed_definitions);
8✔
632
        } else if (auto sequence = dynamic_cast<structured_control_flow::Sequence*>(&child.first)) {
8✔
633
            visit_sequence(analysis_manager, *sequence, undefined, open_definitions, closed_definitions);
×
634
        }
×
635

636
        // handle transitions read
637
        for (auto& entry : child.second.assignments()) {
1,976✔
638
            for (auto& atom : symbolic::atoms(entry.second)) {
125✔
639
                if (symbolic::is_pointer(atom)) {
71✔
640
                    continue;
×
641
                }
×
642
                auto current_user = users.get_user(atom->get_name(), &child.second, Use::READ);
71✔
643

644
                bool found = false;
71✔
645
                for (auto& user : open_definitions) {
71✔
646
                    if (user.first->container() == atom->get_name()) {
63✔
647
                        user.second.insert(current_user);
47✔
648
                        found = true;
47✔
649
                    }
47✔
650
                }
63✔
651
                if (!found) {
71✔
652
                    undefined.insert(current_user);
38✔
653
                }
38✔
654
            }
71✔
655
        }
125✔
656

657
        // handle transitions write
658
        for (auto& entry : child.second.assignments()) {
1,976✔
659
            auto current_user = users.get_user(entry.first->get_name(), &child.second, Use::WRITE);
125✔
660

661
            std::unordered_set<User*> to_close;
125✔
662
            for (auto& user : open_definitions) {
125✔
663
                if (this->closes(analysis_manager, *user.first, *current_user, true)) {
82✔
664
                    to_close.insert(user.first);
12✔
665
                }
12✔
666
            }
82✔
667
            for (auto& user : to_close) {
125✔
668
                closed_definitions.insert({user, open_definitions.at(user)});
12✔
669
                open_definitions.erase(user);
12✔
670
            }
12✔
671
            open_definitions.insert({current_user, {}});
125✔
672
        }
125✔
673
    }
1,976✔
674
}
1,330✔
675

676
bool DataDependencyAnalysis::
677
    supersedes_restrictive(User& previous, User& current, analysis::AnalysisManager& analysis_manager) {
4,121✔
678
    if (previous.container() != current.container()) {
4,121✔
679
        return false;
×
680
    }
×
681
    // Shortcut for scalars
682
    auto& type = this->sdfg_.type(previous.container());
4,121✔
683
    if (dynamic_cast<const types::Scalar*>(&type)) {
4,121✔
684
        return true;
4,063✔
685
    }
4,063✔
686

687
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
58✔
688
        return false;
×
689
    }
×
690

691
    // Conservative shortcut: skip the full symbolic subset analysis and
692
    // assume the previous write does NOT fully cover `current`. Sound
693
    // (downstream consumers treat the read as potentially upward-exposed)
694
    // and avoids ISL queries that have caused fixed-point hangs in the
695
    // wider pass pipeline.
696
    if (!detailed_) {
58✔
697
        return false;
23✔
698
    }
23✔
699

700
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
35✔
701
    auto& previous_subsets = previous.subsets();
35✔
702
    auto& current_subsets = current.subsets();
35✔
703
    auto previous_scope = Users::scope(&previous);
35✔
704
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
35✔
705
    auto current_scope = Users::scope(&current);
35✔
706
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
35✔
707

708
    // Check if previous subset is subset of any current subset
709
    for (auto& previous_subset : previous_subsets) {
35✔
710
        bool found = false;
35✔
711
        for (auto& current_subset : current_subsets) {
35✔
712
            if (symbolic::is_subset(previous_subset, current_subset, previous_assumptions, previous_assumptions)) {
35✔
713
                found = true;
34✔
714
                break;
34✔
715
            }
34✔
716
        }
35✔
717
        if (!found) {
35✔
718
            return false;
1✔
719
        }
1✔
720
    }
35✔
721

722
    return true;
34✔
723
}
35✔
724

725
bool DataDependencyAnalysis::fully_covered(
726
    analysis::AnalysisManager& analysis_manager,
727
    User& current,
728
    const std::unordered_map<User*, std::unordered_set<User*>>& open_definitions
729
) {
311✔
730
    // Scalar reads: container-level open definition is full coverage.
731
    auto& type = this->sdfg_.type(current.container());
311✔
732
    if (dynamic_cast<const types::Scalar*>(&type)) {
311✔
733
        for (auto& w : open_definitions) {
255✔
734
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
255✔
735
                return true;
223✔
736
            }
223✔
737
        }
255✔
NEW
738
        return false;
×
739
    }
223✔
740
    if (this->is_undefined_user(current)) {
88✔
NEW
741
        return false;
×
NEW
742
    }
×
743

744
    // Conservative shortcut: assume coverage holds (treat the read as
745
    // satisfied by some open writer, equivalent to `depends`'s
746
    // intersect-any semantics). Sound: it allows the previous detection
747
    // path to drop the read into `undefined` only when truly unmatched,
748
    // matching pre-`fully_covered` behavior, while skipping ISL queries.
749
    if (!detailed_) {
88✔
750
        for (auto& w : open_definitions) {
78✔
751
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
78✔
752
                return true;
40✔
753
            }
40✔
754
        }
78✔
NEW
755
        return false;
×
756
    }
40✔
757

758
    auto& current_subsets = current.subsets();
48✔
759
    if (current_subsets.empty()) {
48✔
760
        // Symbol use / no real read footprint -- fall back to existence of any
761
        // matching open definition (depends() semantics).
NEW
762
        for (auto& w : open_definitions) {
×
NEW
763
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
×
NEW
764
                return true;
×
NEW
765
            }
×
NEW
766
        }
×
NEW
767
        return false;
×
NEW
768
    }
×
769

770
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
48✔
771
    auto& current_assumptions = assumptions_analysis.get(*Users::scope(&current), true);
48✔
772

773
    // Each read subset must be contained in some single open writer's subset.
774
    for (auto& read_subset : current_subsets) {
48✔
775
        bool covered = false;
48✔
776
        for (auto& w_entry : open_definitions) {
128✔
777
            auto* w = w_entry.first;
128✔
778
            if (w->container() != current.container()) continue;
128✔
779
            if (this->is_undefined_user(*w)) continue;
51✔
780
            auto& w_assumptions = assumptions_analysis.get(*Users::scope(w), true);
51✔
781
            for (auto& w_subset : w->subsets()) {
51✔
782
                if (symbolic::is_subset(read_subset, w_subset, current_assumptions, w_assumptions)) {
51✔
783
                    covered = true;
38✔
784
                    break;
38✔
785
                }
38✔
786
            }
51✔
787
            if (covered) break;
51✔
788
        }
51✔
789
        if (!covered) return false;
48✔
790
    }
48✔
791
    return true;
38✔
792
}
48✔
793

794
bool DataDependencyAnalysis::intersects(User& previous, User& current, analysis::AnalysisManager& analysis_manager) {
34,917✔
795
    if (previous.container() != current.container()) {
34,917✔
796
        return false;
26,415✔
797
    }
26,415✔
798
    // Shortcut for scalars
799
    auto& type = this->sdfg_.type(previous.container());
8,502✔
800
    if (dynamic_cast<const types::Scalar*>(&type)) {
8,502✔
801
        return true;
8,118✔
802
    }
8,118✔
803

804
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
384✔
805
        return true;
×
806
    }
×
807

808
    // Conservative shortcut: assume the two subsets may intersect. Sound
809
    // for dependence tracking (we may report spurious dependencies but
810
    // never miss a real one) and avoids ISL hangs.
811
    if (!detailed_) {
384✔
812
        return true;
150✔
813
    }
150✔
814

815
    auto& previous_subsets = previous.subsets();
234✔
816
    auto& current_subsets = current.subsets();
234✔
817

818
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
234✔
819
    auto previous_scope = Users::scope(&previous);
234✔
820
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
234✔
821
    auto current_scope = Users::scope(&current);
234✔
822
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
234✔
823

824
    // Check if any current subset intersects with any previous subset
825
    bool found = false;
234✔
826
    for (auto& current_subset : current_subsets) {
238✔
827
        for (auto& previous_subset : previous_subsets) {
238✔
828
            if (!symbolic::is_disjoint(current_subset, previous_subset, current_assumptions, previous_assumptions)) {
238✔
829
                found = true;
200✔
830
                break;
200✔
831
            }
200✔
832
        }
238✔
833
        if (found) {
238✔
834
            break;
200✔
835
        }
200✔
836
    }
238✔
837

838
    return found;
234✔
839
}
384✔
840

841
bool DataDependencyAnalysis::
842
    closes(analysis::AnalysisManager& analysis_manager, User& previous, User& current, bool requires_dominance) {
15,721✔
843
    if (previous.container() != current.container()) {
15,721✔
844
        return false;
15,281✔
845
    }
15,281✔
846

847
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
440✔
848
        return false;
×
849
    }
×
850

851
    // Check dominance
852
    if (requires_dominance) {
440✔
853
        auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
328✔
854
        if (!dominance_analysis.post_dominates(current, previous)) {
328✔
855
            return false;
314✔
856
        }
314✔
857
    }
328✔
858

859
    // Previous memlets are subsets of current memlets
860
    auto& type = sdfg_.type(previous.container());
126✔
861
    if (type.type_id() == types::TypeID::Scalar) {
126✔
862
        return true;
23✔
863
    }
23✔
864

865
    // Conservative shortcut: assume `current` does not fully overwrite
866
    // `previous`. Sound (we just keep more open definitions live) and
867
    // avoids ISL hangs.
868
    if (!detailed_) {
103✔
869
        return false;
42✔
870
    }
42✔
871

872
    // Collect memlets and assumptions
873
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
61✔
874
    auto previous_scope = Users::scope(&previous);
61✔
875
    auto current_scope = Users::scope(&current);
61✔
876
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
61✔
877
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
61✔
878

879
    auto& previous_memlets = previous.subsets();
61✔
880
    auto& current_memlets = current.subsets();
61✔
881

882
    for (auto& subset_ : previous_memlets) {
61✔
883
        bool overwritten = false;
61✔
884
        for (auto& subset : current_memlets) {
61✔
885
            if (symbolic::is_subset(subset_, subset, previous_assumptions, current_assumptions)) {
61✔
886
                overwritten = true;
34✔
887
                break;
34✔
888
            }
34✔
889
        }
61✔
890
        if (!overwritten) {
61✔
891
            return false;
27✔
892
        }
27✔
893
    }
61✔
894

895
    return true;
34✔
896
}
61✔
897

898
bool DataDependencyAnalysis::depends(analysis::AnalysisManager& analysis_manager, User& previous, User& current) {
15,026✔
899
    if (previous.container() != current.container()) {
15,026✔
900
        return false;
12,723✔
901
    }
12,723✔
902

903
    // Previous memlets are subsets of current memlets
904
    auto& type = sdfg_.type(previous.container());
2,303✔
905
    if (type.type_id() == types::TypeID::Scalar) {
2,303✔
906
        return true;
2,161✔
907
    }
2,161✔
908

909
    if (this->is_undefined_user(previous)) {
142✔
910
        return true;
×
911
    }
×
912

913
    // Conservative shortcut: assume `current` may depend on `previous`.
914
    if (!detailed_) {
142✔
915
        return true;
63✔
916
    }
63✔
917

918
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
79✔
919
    auto previous_scope = Users::scope(&previous);
79✔
920
    auto current_scope = Users::scope(&current);
79✔
921
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
79✔
922
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
79✔
923

924
    auto& previous_memlets = previous.subsets();
79✔
925
    auto& current_memlets = current.subsets();
79✔
926

927
    bool intersect_any = false;
79✔
928
    for (auto& current_subset : current_memlets) {
79✔
929
        for (auto& previous_subset : previous_memlets) {
79✔
930
            if (!symbolic::is_disjoint(current_subset, previous_subset, current_assumptions, previous_assumptions)) {
79✔
931
                intersect_any = true;
66✔
932
                break;
66✔
933
            }
66✔
934
        }
79✔
935
        if (intersect_any) {
79✔
936
            break;
66✔
937
        }
66✔
938
    }
79✔
939

940
    return intersect_any;
79✔
941
}
142✔
942

943
/****** Public API ******/
944

945
std::unordered_set<User*> DataDependencyAnalysis::defines(User& write) {
240✔
946
    assert(write.use() == Use::WRITE);
240✔
947
    if (results_.find(write.container()) == results_.end()) {
240✔
948
        return {};
×
949
    }
×
950
    auto& raws = results_.at(write.container());
240✔
951
    assert(raws.find(&write) != raws.end());
240✔
952

953
    auto& reads_for_write = raws.at(&write);
240✔
954

955
    std::unordered_set<User*> reads;
240✔
956
    for (auto& entry : reads_for_write) {
965✔
957
        reads.insert(entry);
965✔
958
    }
965✔
959

960
    return reads;
240✔
961
};
240✔
962

963
std::unordered_map<User*, std::unordered_set<User*>> DataDependencyAnalysis::definitions(const std::string& container) {
688✔
964
    if (results_.find(container) == results_.end()) {
688✔
965
        return {};
7✔
966
    }
7✔
967
    return results_.at(container);
681✔
968
};
688✔
969

970
std::unordered_map<User*, std::unordered_set<User*>> DataDependencyAnalysis::defined_by(const std::string& container) {
302✔
971
    auto reads = this->definitions(container);
302✔
972

973
    std::unordered_map<User*, std::unordered_set<User*>> read_to_writes_map;
302✔
974
    for (auto& entry : reads) {
548✔
975
        for (auto& read : entry.second) {
2,840✔
976
            if (read_to_writes_map.find(read) == read_to_writes_map.end()) {
2,840✔
977
                read_to_writes_map[read] = {};
1,439✔
978
            }
1,439✔
979
            read_to_writes_map[read].insert(entry.first);
2,840✔
980
        }
2,840✔
981
    }
548✔
982
    return read_to_writes_map;
302✔
983
};
302✔
984

985
std::unordered_set<User*> DataDependencyAnalysis::defined_by(User& read) {
×
986
    assert(read.use() == Use::READ);
×
987
    auto definitions = this->definitions(read.container());
×
988

989
    std::unordered_set<User*> writes;
×
990
    for (auto& entry : definitions) {
×
991
        for (auto& r : entry.second) {
×
992
            if (&read == r) {
×
993
                writes.insert(entry.first);
×
994
            }
×
995
        }
×
996
    }
×
997
    return writes;
×
998
};
×
999

1000
bool DataDependencyAnalysis::is_undefined_user(User& user) const {
16,615✔
1001
    return user.vertex_ == boost::graph_traits<graph::Graph>::null_vertex();
16,615✔
1002
};
16,615✔
1003

1004
bool DataDependencyAnalysis::has_loop_boundary(structured_control_flow::StructuredLoop& loop) const {
632✔
1005
    return this->loop_boundaries_.find(&loop) != this->loop_boundaries_.end();
632✔
1006
}
632✔
1007

1008
const std::unordered_set<User*>& DataDependencyAnalysis::upward_exposed_reads(structured_control_flow::StructuredLoop&
1009
                                                                                  loop) const {
632✔
1010
    return this->loop_boundaries_.at(&loop).first;
632✔
1011
}
632✔
1012

1013
const std::unordered_map<User*, std::unordered_set<User*>>& DataDependencyAnalysis::
1014
    escaping_definitions(structured_control_flow::StructuredLoop& loop) const {
632✔
1015
    return this->loop_boundaries_.at(&loop).second;
632✔
1016
}
632✔
1017

1018
} // namespace analysis
1019
} // 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