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

daisytuner / docc / 26520678771

27 May 2026 03:22PM UTC coverage: 60.864% (-0.02%) from 60.886%
26520678771

Pull #719

github

web-flow
Merge 99c5e4f9d into 707dadcf8
Pull Request #719: Libnode ptr edges

961 of 1749 new or added lines in 52 files covered. (54.95%)

90 existing lines in 29 files now uncovered.

35222 of 57870 relevant lines covered (60.86%)

11043.61 hits per line

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

91.21
/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()) {};
152✔
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) {
293✔
29
    results_.clear();
293✔
30
    undefined_users_.clear();
293✔
31
    loop_boundaries_.clear();
293✔
32

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

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

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

43
    for (auto& entry : closed_definitions) {
2,865✔
44
        if (results_.find(entry.first->container()) == results_.end()) {
2,865✔
45
            results_.insert({entry.first->container(), {}});
1,599✔
46
        }
1,599✔
47
        results_.at(entry.first->container()).insert(entry);
2,865✔
48
    }
2,865✔
49
};
293✔
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
) {
962✔
60
    auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
962✔
61
    auto& users = analysis_manager.get<analysis::Users>();
962✔
62

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

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

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

83
                    if (use == Use::WRITE) {
919✔
84
                        auto current_user = users.get_user(access_node->data(), access_node, use);
917✔
85

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

98
                        // Start new open definition
99
                        open_definitions.insert({current_user, {}});
917✔
100
                    }
917✔
101
                }
919✔
102
                if (dataflow.out_degree(*access_node) > 0) {
2,145✔
103
                    Use use = Use::READ;
1,290✔
104
                    for (auto& oedge : dataflow.out_edges(*access_node)) {
1,394✔
105
                        if (access_node_type.type_id() == types::TypeID::Pointer &&
1,394✔
106
                            oedge.is_src_pointed_to_address_leak(access_node_type)) {
1,394✔
107
                            if (auto* libConsumer = dynamic_cast<data_flow::LibraryNode*>(&oedge.dst())) {
4✔
108
                                auto access = libConsumer->pointer_access_type(oedge);
1✔
109
                                if (!access || !access->no_capture()) {
1✔
110
                                    use = Use::MOVE; // we know nothing and cannot say anything reliable. Consider the
1✔
111
                                                     // libNode having side effects
112
                                    break;
1✔
113
                                } else {
1✔
NEW
114
                                    if (access->may_contain_reads()) {
×
115
                                        // TODO let handle as a normal read
NEW
116
                                        use = Use::VIEW;
×
NEW
117
                                    } else if (access->may_contain_writes()) {
×
118
                                        // handle as a normal write
NEW
119
                                        use = Use::WRITE;
×
NEW
120
                                        break;
×
NEW
121
                                    }
×
NEW
122
                                }
×
123
                            } else {
3✔
124
                                use = Use::VIEW;
3✔
125
                                break;
3✔
126
                            }
3✔
127
                        } else if (oedge.type() == data_flow::MemletType::Reference ||
1,390✔
128
                                   oedge.type() == data_flow::MemletType::Dereference_Dst) {
1,390✔
UNCOV
129
                            use = Use::VIEW;
×
UNCOV
130
                            break;
×
UNCOV
131
                        }
×
132
                    }
1,394✔
133

134
                    if (use == Use::READ) {
1,290✔
135
                        auto current_user = users.get_user(access_node->data(), access_node, use);
1,286✔
136

137
                        // Assign to open definitions
138
                        bool found_user = false;
1,286✔
139
                        bool found_undefined_user = false;
1,286✔
140
                        for (auto& user : open_definitions) {
1,286✔
141
                            if (this->depends(analysis_manager, *user.first, *current_user)) {
1,187✔
142
                                user.second.insert(current_user);
351✔
143
                                found_user = true;
351✔
144
                                found_undefined_user = this->is_undefined_user(*user.first);
351✔
145
                            }
351✔
146
                        }
1,187✔
147
                        // If no definition found, undefined user found, or
148
                        // the read's subset footprint is only partially
149
                        // covered by the matching open writers, mark as
150
                        // upward-exposed (undefined). `depends` uses
151
                        // intersect-any across the cartesian product of
152
                        // subsets, which silently swallows partial-cover
153
                        // multi-subset reads (e.g. an access node with two
154
                        // out-edges where only one edge is killed inside
155
                        // the current scope).
156
                        if (!found_user || found_undefined_user ||
1,286✔
157
                            !this->fully_covered(analysis_manager, *current_user, open_definitions)) {
1,286✔
158
                            undefined.insert(current_user);
989✔
159
                        }
989✔
160
                    }
1,286✔
161
                }
1,290✔
162
            }
2,145✔
163
        } else if (auto library_node = dynamic_cast<data_flow::LibraryNode*>(node)) {
2,145✔
164
            for (auto& symbol : library_node->symbols()) {
15✔
165
                auto current_user = users.get_user(symbol->get_name(), library_node, Use::READ);
11✔
166

167
                // Assign to open definitions
168
                bool found_user = false;
11✔
169
                bool found_undefined_user = false;
11✔
170
                for (auto& user : open_definitions) {
11✔
171
                    if (this->depends(analysis_manager, *user.first, *current_user)) {
5✔
172
                        user.second.insert(current_user);
×
173
                        found_user = true;
×
174
                        found_undefined_user = this->is_undefined_user(*current_user);
×
175
                    }
×
176
                }
5✔
177
                // If no definition found or undefined user found, mark as undefined
178
                if (!found_user || found_undefined_user) {
11✔
179
                    undefined.insert(current_user);
11✔
180
                }
11✔
181
            }
11✔
182
        }
15✔
183

184
        for (auto& oedge : dataflow.out_edges(*node)) {
3,063✔
185
            std::unordered_set<std::string> used;
2,310✔
186
            for (auto& dim : oedge.subset()) {
2,690✔
187
                for (auto atom : symbolic::atoms(dim)) {
2,973✔
188
                    used.insert(atom->get_name());
2,973✔
189
                }
2,973✔
190
            }
2,690✔
191
            for (auto& atom : used) {
2,924✔
192
                auto current_user = users.get_user(atom, &oedge, Use::READ);
2,924✔
193

194
                // Assign to open definitions
195
                bool found_user = false;
2,924✔
196
                bool found_undefined_user = false;
2,924✔
197
                for (auto& user : open_definitions) {
2,924✔
198
                    if (this->depends(analysis_manager, *user.first, *current_user)) {
2,426✔
199
                        user.second.insert(current_user);
19✔
200
                        found_user = true;
19✔
201
                        found_undefined_user = this->is_undefined_user(*user.first);
19✔
202
                    }
19✔
203
                }
2,426✔
204
                // If no definition found or undefined user found, mark as undefined
205
                if (!found_user || found_undefined_user) {
2,924✔
206
                    undefined.insert(current_user);
2,905✔
207
                }
2,905✔
208
            }
2,924✔
209
        }
2,310✔
210
    }
3,063✔
211
}
962✔
212

213
void DataDependencyAnalysis::visit_for(
214
    analysis::AnalysisManager& analysis_manager,
215
    structured_control_flow::StructuredLoop& for_loop,
216
    std::unordered_set<User*>& undefined,
217
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
218
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
219
) {
946✔
220
    auto& users = analysis_manager.get<analysis::Users>();
946✔
221

222
    // Init - Read
223
    for (auto atom : symbolic::atoms(for_loop.init())) {
946✔
224
        auto current_user = users.get_user(atom->get_name(), &for_loop, Use::READ, true);
174✔
225

226
        // Assign to open definitions
227
        bool found_user = false;
174✔
228
        bool found_undefined_user = false;
174✔
229
        for (auto& user : open_definitions) {
344✔
230
            if (this->depends(analysis_manager, *user.first, *current_user)) {
344✔
231
                user.second.insert(current_user);
3✔
232
                found_user = true;
3✔
233
                found_undefined_user = this->is_undefined_user(*user.first);
3✔
234
            }
3✔
235
        }
344✔
236
        // If no definition found or undefined user found, mark as undefined
237
        if (!found_user || found_undefined_user) {
174✔
238
            undefined.insert(current_user);
172✔
239
        }
172✔
240
    }
174✔
241

242
    // Init - Write
243
    {
946✔
244
        // Write Induction Variable
245
        auto current_user = users.get_user(for_loop.indvar()->get_name(), &for_loop, Use::WRITE, true);
946✔
246

247
        // Close open definitions if possible
248
        std::unordered_map<User*, std::unordered_set<User*>> to_close;
946✔
249
        for (auto& user : open_definitions) {
1,498✔
250
            if (this->closes(analysis_manager, *user.first, *current_user, true)) {
1,498✔
251
                to_close.insert(user);
2✔
252
            }
2✔
253
        }
1,498✔
254
        for (auto& user : to_close) {
946✔
255
            open_definitions.erase(user.first);
2✔
256
            closed_definitions.insert(user);
2✔
257
        }
2✔
258

259
        // Start new open definition
260
        open_definitions.insert({current_user, {}});
946✔
261
    }
946✔
262

263
    // Update - Write
264
    {
946✔
265
        auto current_user = users.get_user(for_loop.indvar()->get_name(), &for_loop, Use::WRITE, false, false, true);
946✔
266
        open_definitions.insert({current_user, {}});
946✔
267
    }
946✔
268

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

273
        // Assign to open definitions
274
        bool found_user = false;
1,945✔
275
        bool found_undefined_user = false;
1,945✔
276
        for (auto& user : open_definitions) {
7,026✔
277
            if (this->depends(analysis_manager, *user.first, *current_user)) {
7,026✔
278
                user.second.insert(current_user);
1,895✔
279
                found_user = true;
1,895✔
280
                found_undefined_user = this->is_undefined_user(*user.first);
1,895✔
281
            }
1,895✔
282
        }
7,026✔
283
        // If no definition found or undefined user found, mark as undefined
284
        if (!found_user || found_undefined_user) {
1,945✔
285
            undefined.insert(current_user);
997✔
286
        }
997✔
287
    }
1,945✔
288

289
    std::unordered_map<User*, std::unordered_set<User*>> open_definitions_for;
946✔
290
    std::unordered_map<User*, std::unordered_set<User*>> closed_definitions_for;
946✔
291
    std::unordered_set<User*> undefined_for;
946✔
292

293
    // Add assumptions for body
294
    visit_sequence(analysis_manager, for_loop.root(), undefined_for, open_definitions_for, closed_definitions_for);
946✔
295

296
    // Update - Read
297
    for (auto atom : symbolic::atoms(for_loop.update())) {
946✔
298
        auto current_user = users.get_user(atom->get_name(), &for_loop, Use::READ, false, false, true);
946✔
299

300
        // Assign to open definitions
301
        bool found_user = false;
946✔
302
        bool found_undefined_user = false;
946✔
303
        for (auto& user : open_definitions_for) {
3,992✔
304
            if (this->depends(analysis_manager, *user.first, *current_user)) {
3,992✔
305
                user.second.insert(current_user);
1✔
306
                found_user = true;
1✔
307
                found_undefined_user = this->is_undefined_user(*user.first);
1✔
308
            }
1✔
309
        }
3,992✔
310
        // If no definition found or undefined user found, mark as undefined
311
        if (!found_user || found_undefined_user) {
946✔
312
            undefined_for.insert(current_user);
945✔
313
        }
945✔
314
    }
946✔
315

316
    // Merge for with outside
317
    auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
946✔
318

319
    // Closed definitions are simply merged
320
    for (auto& entry : closed_definitions_for) {
946✔
321
        closed_definitions.insert(entry);
70✔
322
    }
70✔
323

324
    // Undefined reads are matched or forwarded
325
    for (auto open_read : undefined_for) {
10,123✔
326
        // Simple check: no match or undefined user
327
        std::unordered_set<User*> frontier;
10,123✔
328
        bool found = false;
10,123✔
329
        bool found_undefined_user = false;
10,123✔
330
        for (auto& entry : open_definitions) {
34,917✔
331
            if (intersects(*entry.first, *open_read, analysis_manager)) {
34,917✔
332
                entry.second.insert(open_read);
8,468✔
333
                found = true;
8,468✔
334
                found_undefined_user = this->is_undefined_user(*entry.first);
8,468✔
335
                frontier.insert(entry.first);
8,468✔
336
            }
8,468✔
337
        }
34,917✔
338
        if (!found || found_undefined_user) {
10,123✔
339
            undefined.insert(open_read);
5,767✔
340
            continue;
5,767✔
341
        }
5,767✔
342

343
        // Users found, check if they fully cover the read
344
        bool covered = false;
4,356✔
345
        for (auto& entry : frontier) {
4,721✔
346
            if (!dominance_analysis.dominates(*entry, *open_read)) {
4,721✔
347
                continue;
600✔
348
            }
600✔
349
            bool covers = supersedes_restrictive(*open_read, *entry, analysis_manager);
4,121✔
350
            if (covers) {
4,121✔
351
                covered = true;
4,097✔
352
                break;
4,097✔
353
            }
4,097✔
354
        }
4,121✔
355
        if (!covered) {
4,356✔
356
            undefined.insert(open_read);
259✔
357
        }
259✔
358
    }
4,356✔
359

360
    // Open definitions may close outside open definitions after loop
361
    std::unordered_set<User*> to_close;
946✔
362
    for (auto& previous : open_definitions) {
3,388✔
363
        for (auto& user : open_definitions_for) {
13,349✔
364
            if (this->closes(analysis_manager, *previous.first, *user.first, true)) {
13,349✔
365
                to_close.insert(previous.first);
×
366
                break;
×
367
            }
×
368
        }
13,349✔
369
    }
3,388✔
370
    for (auto& user : to_close) {
946✔
371
        closed_definitions.insert({user, open_definitions.at(user)});
×
372
        open_definitions.erase(user);
×
373
    }
×
374

375
    // Cross-iteration linkage for SCALARS only.
376
    //
377
    // An in-body upward-exposed read may be satisfied (in some non-first
378
    // iteration) by an in-body open writer. We must record those
379
    // (write -> read) edges in `open_definitions_for` so that public
380
    // queries like `defined_by(read)` return the in-body writer in
381
    // addition to any pre-loop write -- otherwise consumers (e.g.
382
    // SymbolPropagation) treat the read as having a single dominating
383
    // definition and incorrectly fold the loop-carried value away.
384
    //
385
    // We restrict to scalar containers because:
386
    //   - For scalars, every access touches the same cell, so the
387
    //     prior-iteration writer always reaches the in-body read --
388
    //     this is sound and precise.
389
    //   - For arrays, deciding whether a writer flows across iterations
390
    //     requires precise per-pair delta analysis (done in LCDA).
391
    //     Adding edges via the over-approximate `depends` predicate
392
    //     would create spurious in-body RAW links that pollute
393
    //     consumers reasoning about precise dataflow on arrays.
394
    //
395
    // This must run BEFORE the snapshot below and BEFORE merging into the
396
    // outer `open_definitions`, since both perform by-value copies of the
397
    // (writer -> readers) sets we are mutating.
398
    for (auto* open_read : undefined_for) {
10,123✔
399
        auto& type = this->sdfg_.type(open_read->container());
10,123✔
400
        if (!dynamic_cast<const types::Scalar*>(&type)) continue;
10,123✔
401
        for (auto& write_entry : open_definitions_for) {
45,679✔
402
            if (write_entry.first->container() != open_read->container()) continue;
45,679✔
403
            if (this->is_undefined_user(*write_entry.first)) continue;
13✔
404
            write_entry.second.insert(open_read);
12✔
405
        }
12✔
406
    }
7,771✔
407

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

411
    // Add open definitions from for to outside
412
    for (auto& entry : open_definitions_for) {
3,992✔
413
        open_definitions.insert(entry);
3,992✔
414
    }
3,992✔
415
}
946✔
416

417
void DataDependencyAnalysis::visit_if_else(
418
    analysis::AnalysisManager& analysis_manager,
419
    structured_control_flow::IfElse& if_else,
420
    std::unordered_set<User*>& undefined,
421
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
422
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
423
) {
30✔
424
    auto& users = analysis_manager.get<analysis::Users>();
30✔
425

426
    // Read Conditions
427
    for (size_t i = 0; i < if_else.size(); i++) {
84✔
428
        auto child = if_else.at(i).second;
54✔
429
        for (auto atom : symbolic::atoms(child)) {
54✔
430
            auto current_user = users.get_user(atom->get_name(), &if_else, Use::READ);
47✔
431

432
            bool found_user = false;
47✔
433
            bool found_undefined_user = false;
47✔
434
            for (auto& user : open_definitions) {
47✔
435
                if (this->depends(analysis_manager, *user.first, *current_user)) {
18✔
436
                    user.second.insert(current_user);
8✔
437
                    found_user = true;
8✔
438
                    found_undefined_user = this->is_undefined_user(*user.first);
8✔
439
                }
8✔
440
            }
18✔
441
            // If no definition found or undefined user found, mark as undefined
442
            if (!found_user || found_undefined_user) {
47✔
443
                undefined.insert(current_user);
39✔
444
            }
39✔
445
        }
47✔
446
    }
54✔
447

448
    std::vector<std::unordered_set<User*>> undefined_branches(if_else.size());
30✔
449
    std::vector<std::unordered_map<User*, std::unordered_set<User*>>> open_definitions_branches(if_else.size());
30✔
450
    std::vector<std::unordered_map<User*, std::unordered_set<User*>>> closed_definitionss_branches(if_else.size());
30✔
451
    for (size_t i = 0; i < if_else.size(); i++) {
84✔
452
        auto& child = if_else.at(i).first;
54✔
453
        visit_sequence(
54✔
454
            analysis_manager,
54✔
455
            child,
54✔
456
            undefined_branches.at(i),
54✔
457
            open_definitions_branches.at(i),
54✔
458
            closed_definitionss_branches.at(i)
54✔
459
        );
54✔
460
    }
54✔
461

462
    // merge partial open reads
463
    for (size_t i = 0; i < if_else.size(); i++) {
84✔
464
        for (auto& entry : undefined_branches.at(i)) {
54✔
465
            bool found_user = false;
34✔
466
            bool found_undefined_user = false;
34✔
467
            for (auto& user : open_definitions) {
34✔
468
                if (this->depends(analysis_manager, *user.first, *entry)) {
10✔
469
                    user.second.insert(entry);
5✔
470
                    found_user = true;
5✔
471
                    found_undefined_user = this->is_undefined_user(*user.first);
5✔
472
                }
5✔
473
            }
10✔
474
            // If no definition found or undefined user found, mark as undefined
475
            if (!found_user || found_undefined_user) {
34✔
476
                undefined.insert(entry);
29✔
477
            }
29✔
478
        }
34✔
479
    }
54✔
480

481
    // merge closed writes
482
    for (auto& closing : closed_definitionss_branches) {
54✔
483
        for (auto& entry : closing) {
54✔
484
            closed_definitions.insert(entry);
×
485
        }
×
486
    }
54✔
487

488
    // Close open reads_after_writes for complete branches
489
    if (if_else.is_complete()) {
30✔
490
        std::unordered_map<User*, std::unordered_set<User*>> to_close;
24✔
491
        std::unordered_set<std::string> candidates;
24✔
492
        std::unordered_set<std::string> candidates_tmp;
24✔
493

494
        /* Complete close open reads_after_writes
495
        1. get candidates from first iteration
496
        2. iterate over all branches and prune candidates
497
        3. find prior writes for remaining candidates
498
        4. close open reads_after_writes for all candidates
499
        */
500
        for (auto& entry : open_definitions_branches.at(0)) {
31✔
501
            candidates.insert(entry.first->container());
31✔
502
        }
31✔
503
        for (auto& entry : closed_definitionss_branches.at(0)) {
24✔
504
            candidates.insert(entry.first->container());
×
505
        }
×
506

507
        for (size_t i = 1; i < if_else.size(); i++) {
48✔
508
            for (auto& entry : open_definitions_branches.at(i)) {
24✔
509
                if (candidates.find(entry.first->container()) != candidates.end()) {
22✔
510
                    candidates_tmp.insert(entry.first->container());
21✔
511
                }
21✔
512
            }
22✔
513
            candidates.swap(candidates_tmp);
24✔
514
            candidates_tmp.clear();
24✔
515
        }
24✔
516

517
        for (auto& entry : open_definitions) {
24✔
518
            if (candidates.find(entry.first->container()) != candidates.end()) {
9✔
519
                to_close.insert(entry);
4✔
520
            }
4✔
521
        }
9✔
522

523
        for (auto& entry : to_close) {
24✔
524
            open_definitions.erase(entry.first);
4✔
525
            closed_definitions.insert(entry);
4✔
526
        }
4✔
527
    } else {
24✔
528
        // Incomplete if-else
529

530
        // In order to determine whether a new read is undefined
531
        // we would need to check whether all open definitions
532
        // jointly dominate the read.
533
        // Since this is expensive, we apply a trick:
534
        // For incomplete if-elses and any newly opened definition in
535
        // any branch, we add an artificial undefined user for that container.
536
        // If we encounter this user later, we know that not all branches defined it.
537
        // Hence, we can mark the read as (partially) undefined.
538

539
        for (auto& branch : open_definitions_branches) {
6✔
540
            for (auto& open_definition : branch) {
6✔
541
                auto write = open_definition.first;
4✔
542
                auto artificial_user = std::make_unique<
4✔
543
                    User>(boost::graph_traits<graph::Graph>::null_vertex(), write->container(), nullptr, Use::WRITE);
4✔
544
                this->undefined_users_.push_back(std::move(artificial_user));
4✔
545
                open_definitions.insert({this->undefined_users_.back().get(), {}});
4✔
546
            }
4✔
547
        }
6✔
548
    }
6✔
549

550
    // Add open definitions from branches to outside
551
    for (auto& branch : open_definitions_branches) {
54✔
552
        for (auto& entry : branch) {
57✔
553
            open_definitions.insert(entry);
57✔
554
        }
57✔
555
    }
54✔
556
}
30✔
557

558
void DataDependencyAnalysis::visit_while(
559
    analysis::AnalysisManager& analysis_manager,
560
    structured_control_flow::While& while_loop,
561
    std::unordered_set<User*>& undefined,
562
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
563
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
564
) {
12✔
565
    std::unordered_map<User*, std::unordered_set<User*>> open_definitions_while;
12✔
566
    std::unordered_map<User*, std::unordered_set<User*>> closed_definitions_while;
12✔
567
    std::unordered_set<User*> undefined_while;
12✔
568

569
    visit_sequence(analysis_manager, while_loop.root(), undefined_while, open_definitions_while, closed_definitions_while);
12✔
570

571
    // Scope-local closed definitions
572
    for (auto& entry : closed_definitions_while) {
12✔
573
        closed_definitions.insert(entry);
2✔
574
    }
2✔
575

576
    for (auto open_read : undefined_while) {
23✔
577
        // Over-Approximation: Add loop-carried dependencies for all open reads
578
        for (auto& entry : open_definitions_while) {
116✔
579
            if (entry.first->container() == open_read->container()) {
116✔
580
                entry.second.insert(open_read);
13✔
581
            }
13✔
582
        }
116✔
583

584
        // Connect to outside
585
        bool found = false;
23✔
586
        for (auto& entry : open_definitions) {
23✔
587
            if (entry.first->container() == open_read->container()) {
1✔
588
                entry.second.insert(open_read);
1✔
589
                found = true;
1✔
590
            }
1✔
591
        }
1✔
592
        if (!found) {
23✔
593
            undefined.insert(open_read);
22✔
594
        }
22✔
595
    }
23✔
596

597
    // Add open definitions from while to outside
598
    for (auto& entry : open_definitions_while) {
39✔
599
        open_definitions.insert(entry);
39✔
600
    }
39✔
601
}
12✔
602

603
void DataDependencyAnalysis::visit_return(
604
    analysis::AnalysisManager& analysis_manager,
605
    structured_control_flow::Return& return_statement,
606
    std::unordered_set<User*>& undefined,
607
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
608
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
609
) {
8✔
610
    auto& users = analysis_manager.get<analysis::Users>();
8✔
611

612
    if (return_statement.is_data() && !return_statement.data().empty()) {
8✔
613
        auto current_user = users.get_user(return_statement.data(), &return_statement, Use::READ);
8✔
614

615
        bool found = false;
8✔
616
        for (auto& user : open_definitions) {
41✔
617
            if (user.first->container() == return_statement.data()) {
41✔
618
                user.second.insert(current_user);
14✔
619
                found = true;
14✔
620
            }
14✔
621
        }
41✔
622
        if (!found) {
8✔
623
            undefined.insert(current_user);
1✔
624
        }
1✔
625
    }
8✔
626

627
    // close all open reads_after_writes
628
    for (auto& entry : open_definitions) {
41✔
629
        closed_definitions.insert(entry);
41✔
630
    }
41✔
631
    open_definitions.clear();
8✔
632
}
8✔
633

634
void DataDependencyAnalysis::visit_sequence(
635
    analysis::AnalysisManager& analysis_manager,
636
    structured_control_flow::Sequence& sequence,
637
    std::unordered_set<User*>& undefined,
638
    std::unordered_map<User*, std::unordered_set<User*>>& open_definitions,
639
    std::unordered_map<User*, std::unordered_set<User*>>& closed_definitions
640
) {
1,322✔
641
    auto& users = analysis_manager.get<analysis::Users>();
1,322✔
642

643
    for (size_t i = 0; i < sequence.size(); i++) {
3,280✔
644
        auto child = sequence.at(i);
1,958✔
645
        if (auto block = dynamic_cast<structured_control_flow::Block*>(&child.first)) {
1,958✔
646
            visit_block(analysis_manager, *block, undefined, open_definitions, closed_definitions);
954✔
647
        } else if (auto for_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child.first)) {
1,004✔
648
            visit_for(analysis_manager, *for_loop, undefined, open_definitions, closed_definitions);
946✔
649
        } else if (auto if_else = dynamic_cast<structured_control_flow::IfElse*>(&child.first)) {
946✔
650
            visit_if_else(analysis_manager, *if_else, undefined, open_definitions, closed_definitions);
30✔
651
        } else if (auto while_loop = dynamic_cast<structured_control_flow::While*>(&child.first)) {
30✔
652
            visit_while(analysis_manager, *while_loop, undefined, open_definitions, closed_definitions);
12✔
653
        } else if (auto return_statement = dynamic_cast<structured_control_flow::Return*>(&child.first)) {
16✔
654
            visit_return(analysis_manager, *return_statement, undefined, open_definitions, closed_definitions);
8✔
655
        } else if (auto sequence = dynamic_cast<structured_control_flow::Sequence*>(&child.first)) {
8✔
656
            visit_sequence(analysis_manager, *sequence, undefined, open_definitions, closed_definitions);
×
657
        }
×
658

659
        // handle transitions read
660
        for (auto& entry : child.second.assignments()) {
1,958✔
661
            for (auto& atom : symbolic::atoms(entry.second)) {
114✔
662
                if (symbolic::is_pointer(atom)) {
63✔
663
                    continue;
×
664
                }
×
665
                auto current_user = users.get_user(atom->get_name(), &child.second, Use::READ);
63✔
666

667
                bool found = false;
63✔
668
                for (auto& user : open_definitions) {
63✔
669
                    if (user.first->container() == atom->get_name()) {
57✔
670
                        user.second.insert(current_user);
43✔
671
                        found = true;
43✔
672
                    }
43✔
673
                }
57✔
674
                if (!found) {
63✔
675
                    undefined.insert(current_user);
34✔
676
                }
34✔
677
            }
63✔
678
        }
114✔
679

680
        // handle transitions write
681
        for (auto& entry : child.second.assignments()) {
1,958✔
682
            auto current_user = users.get_user(entry.first->get_name(), &child.second, Use::WRITE);
114✔
683

684
            std::unordered_set<User*> to_close;
114✔
685
            for (auto& user : open_definitions) {
114✔
686
                if (this->closes(analysis_manager, *user.first, *current_user, true)) {
76✔
687
                    to_close.insert(user.first);
10✔
688
                }
10✔
689
            }
76✔
690
            for (auto& user : to_close) {
114✔
691
                closed_definitions.insert({user, open_definitions.at(user)});
10✔
692
                open_definitions.erase(user);
10✔
693
            }
10✔
694
            open_definitions.insert({current_user, {}});
114✔
695
        }
114✔
696
    }
1,958✔
697
}
1,322✔
698

699
bool DataDependencyAnalysis::
700
    supersedes_restrictive(User& previous, User& current, analysis::AnalysisManager& analysis_manager) {
4,121✔
701
    if (previous.container() != current.container()) {
4,121✔
702
        return false;
×
703
    }
×
704
    // Shortcut for scalars
705
    auto& type = this->sdfg_.type(previous.container());
4,121✔
706
    if (dynamic_cast<const types::Scalar*>(&type)) {
4,121✔
707
        return true;
4,063✔
708
    }
4,063✔
709

710
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
58✔
711
        return false;
×
712
    }
×
713

714
    // Conservative shortcut: skip the full symbolic subset analysis and
715
    // assume the previous write does NOT fully cover `current`. Sound
716
    // (downstream consumers treat the read as potentially upward-exposed)
717
    // and avoids ISL queries that have caused fixed-point hangs in the
718
    // wider pass pipeline.
719
    if (!detailed_) {
58✔
720
        return false;
23✔
721
    }
23✔
722

723
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
35✔
724
    auto& previous_subsets = previous.subsets();
35✔
725
    auto& current_subsets = current.subsets();
35✔
726
    auto previous_scope = Users::scope(&previous);
35✔
727
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
35✔
728
    auto current_scope = Users::scope(&current);
35✔
729
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
35✔
730

731
    // Check if previous subset is subset of any current subset
732
    for (auto& previous_subset : previous_subsets) {
35✔
733
        bool found = false;
35✔
734
        for (auto& current_subset : current_subsets) {
35✔
735
            if (symbolic::is_subset(previous_subset, current_subset, previous_assumptions, previous_assumptions)) {
35✔
736
                found = true;
34✔
737
                break;
34✔
738
            }
34✔
739
        }
35✔
740
        if (!found) {
35✔
741
            return false;
1✔
742
        }
1✔
743
    }
35✔
744

745
    return true;
34✔
746
}
35✔
747

748
bool DataDependencyAnalysis::fully_covered(
749
    analysis::AnalysisManager& analysis_manager,
750
    User& current,
751
    const std::unordered_map<User*, std::unordered_set<User*>>& open_definitions
752
) {
307✔
753
    // Scalar reads: container-level open definition is full coverage.
754
    auto& type = this->sdfg_.type(current.container());
307✔
755
    if (dynamic_cast<const types::Scalar*>(&type)) {
307✔
756
        for (auto& w : open_definitions) {
264✔
757
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
264✔
758
                return true;
223✔
759
            }
223✔
760
        }
264✔
761
        return false;
×
762
    }
223✔
763
    if (this->is_undefined_user(current)) {
84✔
764
        return false;
×
765
    }
×
766

767
    // Conservative shortcut: assume coverage holds (treat the read as
768
    // satisfied by some open writer, equivalent to `depends`'s
769
    // intersect-any semantics). Sound: it allows the previous detection
770
    // path to drop the read into `undefined` only when truly unmatched,
771
    // matching pre-`fully_covered` behavior, while skipping ISL queries.
772
    if (!detailed_) {
84✔
773
        for (auto& w : open_definitions) {
68✔
774
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
68✔
775
                return true;
36✔
776
            }
36✔
777
        }
68✔
778
        return false;
×
779
    }
36✔
780

781
    auto& current_subsets = current.subsets();
48✔
782
    if (current_subsets.empty()) {
48✔
783
        // Symbol use / no real read footprint -- fall back to existence of any
784
        // matching open definition (depends() semantics).
785
        for (auto& w : open_definitions) {
×
786
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
×
787
                return true;
×
788
            }
×
789
        }
×
790
        return false;
×
791
    }
×
792

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

796
    // Each read subset must be contained in some single open writer's subset.
797
    for (auto& read_subset : current_subsets) {
48✔
798
        bool covered = false;
48✔
799
        for (auto& w_entry : open_definitions) {
129✔
800
            auto* w = w_entry.first;
129✔
801
            if (w->container() != current.container()) continue;
129✔
802
            if (this->is_undefined_user(*w)) continue;
51✔
803
            auto& w_assumptions = assumptions_analysis.get(*Users::scope(w), true);
51✔
804
            for (auto& w_subset : w->subsets()) {
51✔
805
                if (symbolic::is_subset(read_subset, w_subset, current_assumptions, w_assumptions)) {
51✔
806
                    covered = true;
38✔
807
                    break;
38✔
808
                }
38✔
809
            }
51✔
810
            if (covered) break;
51✔
811
        }
51✔
812
        if (!covered) return false;
48✔
813
    }
48✔
814
    return true;
38✔
815
}
48✔
816

817
bool DataDependencyAnalysis::intersects(User& previous, User& current, analysis::AnalysisManager& analysis_manager) {
34,917✔
818
    if (previous.container() != current.container()) {
34,917✔
819
        return false;
26,415✔
820
    }
26,415✔
821
    // Shortcut for scalars
822
    auto& type = this->sdfg_.type(previous.container());
8,502✔
823
    if (dynamic_cast<const types::Scalar*>(&type)) {
8,502✔
824
        return true;
8,118✔
825
    }
8,118✔
826

827
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
384✔
828
        return true;
×
829
    }
×
830

831
    // Conservative shortcut: assume the two subsets may intersect. Sound
832
    // for dependence tracking (we may report spurious dependencies but
833
    // never miss a real one) and avoids ISL hangs.
834
    if (!detailed_) {
384✔
835
        return true;
150✔
836
    }
150✔
837

838
    auto& previous_subsets = previous.subsets();
234✔
839
    auto& current_subsets = current.subsets();
234✔
840

841
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
234✔
842
    auto previous_scope = Users::scope(&previous);
234✔
843
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
234✔
844
    auto current_scope = Users::scope(&current);
234✔
845
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
234✔
846

847
    // Check if any current subset intersects with any previous subset
848
    bool found = false;
234✔
849
    for (auto& current_subset : current_subsets) {
238✔
850
        for (auto& previous_subset : previous_subsets) {
238✔
851
            if (!symbolic::is_disjoint(current_subset, previous_subset, current_assumptions, previous_assumptions)) {
238✔
852
                found = true;
200✔
853
                break;
200✔
854
            }
200✔
855
        }
238✔
856
        if (found) {
238✔
857
            break;
200✔
858
        }
200✔
859
    }
238✔
860

861
    return found;
234✔
862
}
384✔
863

864
bool DataDependencyAnalysis::
865
    closes(analysis::AnalysisManager& analysis_manager, User& previous, User& current, bool requires_dominance) {
15,711✔
866
    if (previous.container() != current.container()) {
15,711✔
867
        return false;
15,275✔
868
    }
15,275✔
869

870
    if (this->is_undefined_user(previous) || this->is_undefined_user(current)) {
436✔
871
        return false;
×
872
    }
×
873

874
    // Check dominance
875
    if (requires_dominance) {
436✔
876
        auto& dominance_analysis = analysis_manager.get<analysis::DominanceAnalysis>();
326✔
877
        if (!dominance_analysis.post_dominates(current, previous)) {
326✔
878
            return false;
314✔
879
        }
314✔
880
    }
326✔
881

882
    // Previous memlets are subsets of current memlets
883
    auto& type = sdfg_.type(previous.container());
122✔
884
    if (type.type_id() == types::TypeID::Scalar) {
122✔
885
        return true;
19✔
886
    }
19✔
887

888
    // Conservative shortcut: assume `current` does not fully overwrite
889
    // `previous`. Sound (we just keep more open definitions live) and
890
    // avoids ISL hangs.
891
    if (!detailed_) {
103✔
892
        return false;
42✔
893
    }
42✔
894

895
    // Collect memlets and assumptions
896
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
61✔
897
    auto previous_scope = Users::scope(&previous);
61✔
898
    auto current_scope = Users::scope(&current);
61✔
899
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
61✔
900
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
61✔
901

902
    auto& previous_memlets = previous.subsets();
61✔
903
    auto& current_memlets = current.subsets();
61✔
904

905
    for (auto& subset_ : previous_memlets) {
61✔
906
        bool overwritten = false;
61✔
907
        for (auto& subset : current_memlets) {
61✔
908
            if (symbolic::is_subset(subset_, subset, previous_assumptions, current_assumptions)) {
61✔
909
                overwritten = true;
34✔
910
                break;
34✔
911
            }
34✔
912
        }
61✔
913
        if (!overwritten) {
61✔
914
            return false;
27✔
915
        }
27✔
916
    }
61✔
917

918
    return true;
34✔
919
}
61✔
920

921
bool DataDependencyAnalysis::depends(analysis::AnalysisManager& analysis_manager, User& previous, User& current) {
15,008✔
922
    if (previous.container() != current.container()) {
15,008✔
923
        return false;
12,713✔
924
    }
12,713✔
925

926
    // Previous memlets are subsets of current memlets
927
    auto& type = sdfg_.type(previous.container());
2,295✔
928
    if (type.type_id() == types::TypeID::Scalar) {
2,295✔
929
        return true;
2,159✔
930
    }
2,159✔
931

932
    if (this->is_undefined_user(previous)) {
136✔
933
        return true;
×
934
    }
×
935

936
    // Conservative shortcut: assume `current` may depend on `previous`.
937
    if (!detailed_) {
136✔
938
        return true;
57✔
939
    }
57✔
940

941
    auto& assumptions_analysis = analysis_manager.get<analysis::AssumptionsAnalysis>();
79✔
942
    auto previous_scope = Users::scope(&previous);
79✔
943
    auto current_scope = Users::scope(&current);
79✔
944
    auto& previous_assumptions = assumptions_analysis.get(*previous_scope, true);
79✔
945
    auto& current_assumptions = assumptions_analysis.get(*current_scope, true);
79✔
946

947
    auto& previous_memlets = previous.subsets();
79✔
948
    auto& current_memlets = current.subsets();
79✔
949

950
    bool intersect_any = false;
79✔
951
    for (auto& current_subset : current_memlets) {
79✔
952
        for (auto& previous_subset : previous_memlets) {
79✔
953
            if (!symbolic::is_disjoint(current_subset, previous_subset, current_assumptions, previous_assumptions)) {
79✔
954
                intersect_any = true;
66✔
955
                break;
66✔
956
            }
66✔
957
        }
79✔
958
        if (intersect_any) {
79✔
959
            break;
66✔
960
        }
66✔
961
    }
79✔
962

963
    return intersect_any;
79✔
964
}
136✔
965

966
/****** Public API ******/
967

968
std::unordered_set<User*> DataDependencyAnalysis::defines(User& write) {
241✔
969
    assert(write.use() == Use::WRITE);
241✔
970
    if (results_.find(write.container()) == results_.end()) {
241✔
971
        return {};
×
972
    }
×
973
    auto& raws = results_.at(write.container());
241✔
974
    assert(raws.find(&write) != raws.end());
241✔
975

976
    auto& reads_for_write = raws.at(&write);
241✔
977

978
    std::unordered_set<User*> reads;
241✔
979
    for (auto& entry : reads_for_write) {
967✔
980
        reads.insert(entry);
967✔
981
    }
967✔
982

983
    return reads;
241✔
984
};
241✔
985

986
std::unordered_map<User*, std::unordered_set<User*>> DataDependencyAnalysis::definitions(const std::string& container) {
688✔
987
    if (results_.find(container) == results_.end()) {
688✔
988
        return {};
7✔
989
    }
7✔
990
    return results_.at(container);
681✔
991
};
688✔
992

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

996
    std::unordered_map<User*, std::unordered_set<User*>> read_to_writes_map;
302✔
997
    for (auto& entry : reads) {
548✔
998
        for (auto& read : entry.second) {
2,840✔
999
            if (read_to_writes_map.find(read) == read_to_writes_map.end()) {
2,840✔
1000
                read_to_writes_map[read] = {};
1,439✔
1001
            }
1,439✔
1002
            read_to_writes_map[read].insert(entry.first);
2,840✔
1003
        }
2,840✔
1004
    }
548✔
1005
    return read_to_writes_map;
302✔
1006
};
302✔
1007

1008
std::unordered_set<User*> DataDependencyAnalysis::defined_by(User& read) {
×
1009
    assert(read.use() == Use::READ);
×
1010
    auto definitions = this->definitions(read.container());
×
1011

1012
    std::unordered_set<User*> writes;
×
1013
    for (auto& entry : definitions) {
×
1014
        for (auto& r : entry.second) {
×
1015
            if (&read == r) {
×
1016
                writes.insert(entry.first);
×
1017
            }
×
1018
        }
×
1019
    }
×
1020
    return writes;
×
1021
};
×
1022

1023
bool DataDependencyAnalysis::is_undefined_user(User& user) const {
16,585✔
1024
    return user.vertex_ == boost::graph_traits<graph::Graph>::null_vertex();
16,585✔
1025
};
16,585✔
1026

1027
bool DataDependencyAnalysis::has_loop_boundary(structured_control_flow::StructuredLoop& loop) const {
632✔
1028
    return this->loop_boundaries_.find(&loop) != this->loop_boundaries_.end();
632✔
1029
}
632✔
1030

1031
const std::unordered_set<User*>& DataDependencyAnalysis::upward_exposed_reads(structured_control_flow::StructuredLoop&
1032
                                                                                  loop) const {
632✔
1033
    return this->loop_boundaries_.at(&loop).first;
632✔
1034
}
632✔
1035

1036
const std::unordered_map<User*, std::unordered_set<User*>>& DataDependencyAnalysis::
1037
    escaping_definitions(structured_control_flow::StructuredLoop& loop) const {
632✔
1038
    return this->loop_boundaries_.at(&loop).second;
632✔
1039
}
632✔
1040

1041
} // namespace analysis
1042
} // 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