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

daisytuner / docc / 26556322966

27 May 2026 03:45PM UTC coverage: 60.869% (-0.02%) from 60.886%
26556322966

push

github

web-flow
Libnode ptr edges (#719)

Migrating SDFGs to treat pointers as inputs to libNodes / Calls as scalars.
A pointer will only appear in an output edge if its actually returned from the function (like malloc).

* Stdlib, Blas and Tensor Matmul nodes were migrated to this new format. Other, currently transitory Tensor Nodes are not yet migrated.
* DOCC version was bumped to incorporate previous docc-llvm versions (up to 0.4.0) that had been counted separately.
! Until all passes consider the use / leak of pointers as uncertainty / hiding potential writes, TensorNodes are declared as general side-effect.
* Lots of utility functions to centralize the creation (and edges) of various libNodes that needed to be changed.
* Fixed & unified docc paths across python and llvm front-ends.
* Skip BlockFusion test that fails to its libNodes currently having side effects
~ Prevent a crash in DotViz when using symbolic offsets into structs
* Removing old ConstProp pass, it is not safe for the new pointer representation and should not be all too critical

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

87 existing lines in 28 files now uncovered.

35225 of 57870 relevant lines covered (60.87%)

11046.32 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,719✔
346
            if (!dominance_analysis.dominates(*entry, *open_read)) {
4,719✔
347
                continue;
598✔
348
            }
598✔
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) {
267✔
757
            if (w.first->container() == current.container() && !this->is_undefined_user(*w.first)) {
267✔
758
                return true;
223✔
759
            }
223✔
760
        }
267✔
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) {
131✔
800
            auto* w = w_entry.first;
131✔
801
            if (w->container() != current.container()) continue;
131✔
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