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

daisytuner / docc / 25102159278

29 Apr 2026 09:50AM UTC coverage: 64.258%. First build
25102159278

Pull #692

github

web-flow
Merge 1d6311de9 into ebd5ecdd9
Pull Request #692: adds extended unit tests for memory layout analysis

84 of 144 new or added lines in 2 files covered. (58.33%)

30921 of 48120 relevant lines covered (64.26%)

574.96 hits per line

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

73.57
/sdfg/src/analysis/memory_layout_analysis.cpp
1
#include "sdfg/analysis/memory_layout_analysis.h"
2

3
#include <optional>
4
#include <set>
5
#include <unordered_set>
6

7
#include "sdfg/analysis/assumptions_analysis.h"
8
#include "sdfg/data_flow/access_node.h"
9
#include "sdfg/structured_control_flow/block.h"
10
#include "sdfg/structured_control_flow/if_else.h"
11
#include "sdfg/structured_control_flow/sequence.h"
12
#include "sdfg/structured_control_flow/structured_loop.h"
13
#include "sdfg/structured_control_flow/while.h"
14
#include "sdfg/symbolic/delinearization.h"
15
#include "sdfg/symbolic/extreme_values.h"
16
#include "sdfg/symbolic/polynomials.h"
17

18
namespace sdfg {
19
namespace analysis {
20

21
namespace {
22
// Collect StructuredLoop nodes that are direct children of the given node,
23
// stopping at loop boundaries (does not recurse into nested loops).
24
void collect_direct_child_loops(
25
    structured_control_flow::ControlFlowNode& node, std::set<const structured_control_flow::StructuredLoop*>& result
26
) {
36✔
27
    if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&node)) {
36✔
28
        result.insert(loop);
11✔
29
        return;
11✔
30
    }
11✔
31
    if (auto* seq = dynamic_cast<structured_control_flow::Sequence*>(&node)) {
25✔
32
        for (size_t i = 0; i < seq->size(); i++) {
36✔
33
            collect_direct_child_loops(seq->at(i).first, result);
18✔
34
        }
18✔
35
    } else if (auto* ife = dynamic_cast<structured_control_flow::IfElse*>(&node)) {
18✔
NEW
36
        for (size_t i = 0; i < ife->size(); i++) {
×
NEW
37
            collect_direct_child_loops(ife->at(i).first, result);
×
NEW
38
        }
×
39
    } else if (auto* w = dynamic_cast<structured_control_flow::While*>(&node)) {
7✔
NEW
40
        collect_direct_child_loops(w->root(), result);
×
NEW
41
    }
×
42
}
25✔
43
} // namespace
44

45
MemoryLayoutAnalysis::MemoryLayoutAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg) {}
7✔
46

47
void MemoryLayoutAnalysis::run(analysis::AnalysisManager& analysis_manager) {
7✔
48
    accesses_.clear();
7✔
49
    tiles_.clear();
7✔
50
    traverse(sdfg_.root(), analysis_manager);
7✔
51
}
7✔
52

53
void MemoryLayoutAnalysis::
54
    traverse(structured_control_flow::ControlFlowNode& node, analysis::AnalysisManager& analysis_manager) {
50✔
55
    if (auto block = dynamic_cast<structured_control_flow::Block*>(&node)) {
50✔
56
        process_block(*block, analysis_manager);
7✔
57
    } else if (auto sequence = dynamic_cast<structured_control_flow::Sequence*>(&node)) {
43✔
58
        for (size_t i = 0; i < sequence->size(); i++) {
50✔
59
            traverse(sequence->at(i).first, analysis_manager);
25✔
60
        }
25✔
61
    } else if (auto if_else = dynamic_cast<structured_control_flow::IfElse*>(&node)) {
25✔
62
        for (size_t i = 0; i < if_else->size(); i++) {
×
63
            traverse(if_else->at(i).first, analysis_manager);
×
64
        }
×
65
    } else if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(&node)) {
18✔
66
        traverse(while_stmt->root(), analysis_manager);
×
67
    } else if (auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&node)) {
18✔
68
        // Snapshot current memlets before traversing loop body
69
        std::vector<const data_flow::Memlet*> memlets_before;
18✔
70
        memlets_before.reserve(accesses_.size());
18✔
71
        for (const auto& entry : accesses_) {
18✔
72
            memlets_before.push_back(entry.first);
×
73
        }
×
74

75
        // Snapshot tile keys before traversal
76
        std::set<std::pair<const structured_control_flow::StructuredLoop*, std::string>> tiles_before;
18✔
77
        for (const auto& entry : tiles_) {
18✔
78
            tiles_before.insert(entry.first);
×
79
        }
×
80

81
        traverse(loop->root(), analysis_manager);
18✔
82

83
        // Merge layouts for containers accessed within this loop
84
        merge_loop_layouts(*loop, memlets_before, tiles_before, analysis_manager);
18✔
85
    }
18✔
86
    // Break, Continue, Return nodes don't contain blocks
87
}
50✔
88

89
void MemoryLayoutAnalysis::
90
    process_block(structured_control_flow::Block& block, analysis::AnalysisManager& analysis_manager) {
7✔
91
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
7✔
92
    auto& assumptions = assumptions_analysis.get(block);
7✔
93

94
    auto& dfg = block.dataflow();
7✔
95
    for (auto& memlet : dfg.edges()) {
18✔
96
        const auto& subset = memlet.subset();
18✔
97
        if (subset.empty()) {
18✔
98
            continue;
×
99
        }
×
100

101
        // Get container name from the AccessNode (either src or dst)
102
        std::string container_name;
18✔
103
        if (auto* access = dynamic_cast<const data_flow::AccessNode*>(&memlet.src())) {
18✔
104
            container_name = access->data();
11✔
105
        } else if (auto* access = dynamic_cast<const data_flow::AccessNode*>(&memlet.dst())) {
11✔
106
            container_name = access->data();
7✔
107
        } else {
7✔
108
            continue; // Skip memlets without AccessNode
×
109
        }
×
110

111
        auto& base_type = memlet.base_type();
18✔
112
        switch (base_type.type_id()) {
18✔
113
            case types::TypeID::Scalar:
×
114
            case types::TypeID::Structure:
×
115
                continue; // Skip scalars and structures
×
116
            case types::TypeID::Tensor: {
×
117
                // Tensor types already contain layout information, so we can directly store it without delinearization
118
                auto& tensor_type = dynamic_cast<const types::Tensor&>(memlet.base_type());
×
119

120
                MemoryLayout layout(tensor_type.shape(), tensor_type.strides(), tensor_type.offset());
×
121
                MemoryAccess layout_info{container_name, subset, layout, true};
×
122
                this->accesses_.emplace(&memlet, layout_info);
×
123
                continue;
×
124
            }
×
125
            case types::TypeID::Array: {
×
126
                // Arrays are c-like stack array, so we can infer a simple row-major layout without needing
127
                // delinearization
128
                auto* array_type = dynamic_cast<const types::Array*>(&memlet.base_type());
×
129
                symbolic::MultiExpression shape = {array_type->num_elements()};
×
130
                while (array_type->element_type().type_id() == types::TypeID::Array) {
×
131
                    array_type = dynamic_cast<const types::Array*>(&array_type->element_type());
×
132
                }
×
133
                if (array_type->element_type().type_id() != types::TypeID::Scalar) {
×
134
                    continue; // Skip non-scalar arrays
×
135
                }
×
136

137
                MemoryLayout layout(shape);
×
138
                MemoryAccess layout_info{container_name, subset, layout, true};
×
139
                this->accesses_.emplace(&memlet, layout_info);
×
140
                continue;
×
141
            }
×
142
            case types::TypeID::Pointer: {
18✔
143
                // For pointers, we attempt to delinearize the access pattern to infer the layout based
144
                // on assumptions from loop bounds
145
                auto* pointer_type = dynamic_cast<const types::Pointer*>(&memlet.base_type());
18✔
146
                if (pointer_type->pointee_type().type_id() != types::TypeID::Scalar) {
18✔
147
                    continue; // Skip non-scalar pointers
×
148
                }
×
149

150
                if (subset.size() != 1) {
18✔
151
                    continue; // Require full linearization
×
152
                }
×
153
                auto& linearized_expr = subset.at(0);
18✔
154

155
                auto result = symbolic::delinearize(linearized_expr, assumptions);
18✔
156
                if (!result.success) {
18✔
157
                    continue; // Delinearization failed, skip
×
158
                }
×
159

160
                // Delinearization returns N indices but only N-1 dimensions (from stride division)
161
                // The first dimension is unbounded - insert a placeholder that will be filled in by merge
162
                // Using a special symbol as placeholder for the first dimension
163
                symbolic::MultiExpression shape;
18✔
164
                shape.push_back(symbolic::symbol("__unbounded__"));
18✔
165
                for (const auto& dim : result.dimensions) {
22✔
166
                    shape.push_back(dim);
22✔
167
                }
22✔
168

169
                // Store symbolic indices and dimensions with unbounded first dimension
170
                // The merge phase will attempt to bound the first dimension using loop assumptions
171
                MemoryLayout layout(shape);
18✔
172
                MemoryAccess layout_info{container_name, result.indices, layout, false};
18✔
173
                this->accesses_.emplace(&memlet, layout_info);
18✔
174
                continue;
18✔
175
            }
18✔
176
            default:
×
177
                continue; // Skip unsupported types
×
178
        }
18✔
179
    }
18✔
180
}
7✔
181

182
const MemoryAccess* MemoryLayoutAnalysis::access(const data_flow::Memlet& memlet) const {
17✔
183
    auto layout_it = accesses_.find(&memlet);
17✔
184
    if (layout_it == accesses_.end()) {
17✔
185
        return nullptr;
×
186
    }
×
187
    return &layout_it->second;
17✔
188
}
17✔
189

190
void MemoryLayoutAnalysis::merge_loop_layouts(
191
    structured_control_flow::StructuredLoop& loop,
192
    const std::vector<const data_flow::Memlet*>& memlets_before,
193
    const std::set<std::pair<const structured_control_flow::StructuredLoop*, std::string>>& tiles_before,
194
    analysis::AnalysisManager& analysis_manager
195
) {
18✔
196
    // Convert memlets_before to a set for O(1) lookup
197
    std::unordered_set<const data_flow::Memlet*> before_set(memlets_before.begin(), memlets_before.end());
18✔
198

199
    // Group all new accesses by container
200
    std::unordered_map<std::string, std::vector<const data_flow::Memlet*>> all_container_groups;
18✔
201
    for (auto& [memlet_ptr, acc] : accesses_) {
44✔
202
        if (before_set.find(memlet_ptr) != before_set.end()) {
44✔
203
            continue;
×
204
        }
×
205
        all_container_groups[acc.container].push_back(memlet_ptr);
44✔
206
    }
44✔
207

208
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
18✔
209
    auto& assumptions = assumptions_analysis.get(loop.root());
18✔
210
    // Start with SDFG-level parameters (read-only arguments like N, M)
211
    // then add any additional constant symbols from loop assumptions
212
    symbolic::SymbolSet parameters = assumptions_analysis.parameters();
18✔
213
    for (auto& entry : assumptions) {
64✔
214
        if (symbolic::eq(entry.first, loop.indvar())) {
64✔
215
            continue; // Skip induction variable itself
18✔
216
        }
18✔
217

218
        if (entry.second.constant()) {
46✔
219
            parameters.insert(entry.first);
46✔
220
        }
46✔
221
    }
46✔
222

223
    // Find direct child loops of this loop (not grandchildren)
224
    std::set<const structured_control_flow::StructuredLoop*> direct_child_loops;
18✔
225
    collect_direct_child_loops(loop.root(), direct_child_loops);
18✔
226

227
    for (auto& [container, memlets] : all_container_groups) {
20✔
228
        if (memlets.empty()) continue;
20✔
229

230
        // Find inner tiles from direct child loops only
231
        std::vector<const MemoryTile*> inner_tiles;
20✔
232
        for (auto& [key, tile] : tiles_) {
21✔
233
            if (tiles_before.count(key) > 0) continue;
21✔
234
            if (key.second != container) continue;
21✔
235
            if (direct_child_loops.count(key.first) == 0) continue;
17✔
236
            inner_tiles.push_back(&tile);
12✔
237
        }
12✔
238

239
        size_t ndims = 0;
20✔
240
        MemoryLayout reference_layout({symbolic::one()});
20✔
241
        // Separate min/max index lists to avoid unnecessary symbolic min/max
242
        std::vector<std::vector<symbolic::Expression>> min_indices;
20✔
243
        std::vector<std::vector<symbolic::Expression>> max_indices;
20✔
244

245
        if (!inner_tiles.empty()) {
20✔
246
            // Use inner tile min/max as representative values
247
            // Inner tiles have already resolved inner loop variables to their bounds
248
            ndims = inner_tiles[0]->min_subset.size();
12✔
249
            reference_layout = inner_tiles[0]->layout;
12✔
250
            min_indices.resize(ndims);
12✔
251
            max_indices.resize(ndims);
12✔
252

253
            for (const auto* tile : inner_tiles) {
12✔
254
                if (tile->min_subset.size() != ndims) continue;
12✔
255
                for (size_t d = 0; d < ndims; ++d) {
40✔
256
                    min_indices[d].push_back(tile->min_subset[d]);
28✔
257
                    max_indices[d].push_back(tile->max_subset[d]);
28✔
258
                }
28✔
259
            }
12✔
260
        } else {
12✔
261
            // Use raw access indices (no inner tiles available)
262
            auto& first_access = accesses_.at(memlets[0]);
8✔
263
            auto& reference_shape = first_access.layout.shape();
8✔
264
            ndims = reference_shape.size();
8✔
265
            reference_layout = first_access.layout;
8✔
266
            min_indices.resize(ndims);
8✔
267
            max_indices.resize(ndims);
8✔
268

269
            bool consistent = true;
8✔
270
            for (const auto* memlet_ptr : memlets) {
18✔
271
                auto& acc = accesses_.at(memlet_ptr);
18✔
272
                auto& shape = acc.layout.shape();
18✔
273

274
                if (shape.size() != ndims) {
18✔
275
                    consistent = false;
×
276
                    break;
×
277
                }
×
278
                // Check inner dimensions match (all except first which may be unbounded)
279
                for (size_t d = 1; d < ndims; ++d) {
40✔
280
                    if (!symbolic::eq(shape[d], reference_shape[d])) {
22✔
281
                        consistent = false;
×
282
                        break;
×
283
                    }
×
284
                }
22✔
285
                if (!consistent) break;
18✔
286

287
                // Collect indices for each dimension
288
                if (acc.subset.size() != ndims) {
18✔
289
                    consistent = false;
×
290
                    break;
×
291
                }
×
292
                for (size_t d = 0; d < ndims; ++d) {
58✔
293
                    min_indices[d].push_back(acc.subset[d]);
40✔
294
                    max_indices[d].push_back(acc.subset[d]);
40✔
295
                }
40✔
296
            }
18✔
297

298
            if (!consistent) continue;
8✔
299
        }
8✔
300

301
        if (ndims == 0) continue;
20✔
302

303
        // Compute min/max bounds for each dimension
304
        data_flow::Subset min_subset;
20✔
305
        data_flow::Subset max_subset;
20✔
306
        bool all_bounded = true;
20✔
307

308
        for (size_t d = 0; d < ndims; ++d) {
66✔
309
            symbolic::Expression dim_min = SymEngine::null;
46✔
310
            symbolic::Expression dim_max = SymEngine::null;
46✔
311

312
            // Compute dim_min from min_indices
313
            for (const auto& idx : min_indices[d]) {
68✔
314
                auto lb = symbolic::minimum(idx, parameters, assumptions, true);
68✔
315
                if (lb.is_null()) {
68✔
316
                    lb = symbolic::minimum(idx, parameters, assumptions, false);
×
317
                }
×
318
                if (lb.is_null()) {
68✔
319
                    all_bounded = false;
×
320
                    break;
×
321
                }
×
322
                if (dim_min.is_null()) {
68✔
323
                    dim_min = lb;
46✔
324
                } else {
46✔
325
                    dim_min = symbolic::min(dim_min, lb);
22✔
326
                }
22✔
327
            }
68✔
328
            if (!all_bounded) break;
46✔
329

330
            // Compute dim_max from max_indices
331
            for (const auto& idx : max_indices[d]) {
68✔
332
                auto ub = symbolic::maximum(idx, parameters, assumptions, true);
68✔
333
                if (ub.is_null()) {
68✔
334
                    ub = symbolic::maximum(idx, parameters, assumptions, false);
×
335
                }
×
336
                if (ub.is_null()) {
68✔
337
                    all_bounded = false;
×
338
                    break;
×
339
                }
×
340
                if (dim_max.is_null()) {
68✔
341
                    dim_max = ub;
46✔
342
                } else {
46✔
343
                    dim_max = symbolic::max(dim_max, ub);
22✔
344
                }
22✔
345
            }
68✔
346
            if (!all_bounded) break;
46✔
347

348
            min_subset.push_back(symbolic::simplify(dim_min));
46✔
349
            max_subset.push_back(symbolic::simplify(dim_max));
46✔
350
        }
46✔
351

352
        if (!all_bounded) continue;
20✔
353

354
        // Store this loop's tile with the original memory layout
355
        tiles_.insert({{&loop, container}, MemoryTile{container, min_subset, max_subset, reference_layout, true}});
20✔
356
    }
20✔
357
}
18✔
358

359
const MemoryTile* MemoryLayoutAnalysis::
360
    tile(const structured_control_flow::StructuredLoop& loop, const std::string& container) const {
20✔
361
    auto key = std::make_pair(&loop, container);
20✔
362
    auto it = tiles_.find(key);
20✔
363
    if (it == tiles_.end()) {
20✔
364
        return nullptr;
×
365
    }
×
366
    return &it->second;
20✔
367
}
20✔
368

369
symbolic::MultiExpression MemoryTile::extents() const {
18✔
370
    symbolic::MultiExpression result;
18✔
371
    for (size_t d = 0; d < min_subset.size(); ++d) {
60✔
372
        result.push_back(symbolic::simplify(symbolic::add(symbolic::sub(max_subset[d], min_subset[d]), symbolic::one()))
42✔
373
        );
42✔
374
    }
42✔
375
    return result;
18✔
376
}
18✔
377

378
std::pair<symbolic::Expression, symbolic::Expression> MemoryTile::contiguous_range() const {
18✔
379
    auto& strides = layout.strides();
18✔
380
    auto first = layout.offset();
18✔
381
    auto last = layout.offset();
18✔
382
    for (size_t d = 0; d < min_subset.size(); ++d) {
60✔
383
        first = symbolic::add(first, symbolic::mul(strides[d], min_subset[d]));
42✔
384
        last = symbolic::add(last, symbolic::mul(strides[d], max_subset[d]));
42✔
385
    }
42✔
386
    return {symbolic::simplify(first), symbolic::simplify(last)};
18✔
387
}
18✔
388

389
} // namespace analysis
390
} // 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