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

daisytuner / docc / 27141742426

08 Jun 2026 01:41PM UTC coverage: 61.302% (+0.03%) from 61.275%
27141742426

Pull #739

github

web-flow
Merge 2fce81703 into aacd50c09
Pull Request #739: Segformer

52 of 54 new or added lines in 6 files covered. (96.3%)

89 existing lines in 3 files now uncovered.

35633 of 58127 relevant lines covered (61.3%)

11004.55 hits per line

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

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

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

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

19
namespace sdfg {
20
namespace analysis {
21

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

46
MemoryLayoutAnalysis::MemoryLayoutAnalysis(StructuredSDFG& sdfg) : Analysis(sdfg) {}
224✔
47

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

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

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

83
        traverse(loop->root(), analysis_manager);
845✔
84

85
        // Merge layouts for containers accessed within this loop
86
        merge_loop_layouts(*loop, memlets_before, tiles_before, analysis_manager);
845✔
87
    }
845✔
88
    // Break, Continue, Return nodes don't contain blocks
89
}
2,581✔
90

91
void MemoryLayoutAnalysis::
92
    process_block(structured_control_flow::Block& block, analysis::AnalysisManager& analysis_manager) {
642✔
93
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
642✔
94
    // Use trivial bounds (type-derived, e.g. unsigned >= 0) so delinearization
95
    // can soundly discharge non-negativity proof obligations on parameters.
96
    auto& assumptions = assumptions_analysis.get(block, /*include_trivial_bounds=*/true);
642✔
97

98
    auto& dfg = block.dataflow();
642✔
99
    for (auto& memlet : dfg.edges()) {
1,895✔
100
        const auto& subset = memlet.subset();
1,895✔
101
        if (subset.empty()) {
1,895✔
102
            continue;
553✔
103
        }
553✔
104

105
        // Get container name from the AccessNode (either src or dst)
106
        std::string container_name;
1,342✔
107
        if (auto* access = dynamic_cast<const data_flow::AccessNode*>(&memlet.src())) {
1,342✔
108
            container_name = access->data();
871✔
109
        } else if (auto* access = dynamic_cast<const data_flow::AccessNode*>(&memlet.dst())) {
871✔
110
            container_name = access->data();
471✔
111
        } else {
471✔
112
            continue; // Skip memlets without AccessNode
×
113
        }
×
114

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

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

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

154
                if (subset.size() != 1) {
361✔
155
                    continue; // Require full linearization
×
156
                }
×
157
                auto& linearized_expr = subset.at(0);
361✔
158

159
                auto result = symbolic::delinearize(linearized_expr, assumptions);
361✔
160
                if (!result.success) {
361✔
161
                    continue; // Delinearization failed, skip
13✔
162
                }
13✔
163

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

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

186
const MemoryAccess* MemoryLayoutAnalysis::access(const data_flow::Memlet& memlet) const {
4,420✔
187
    auto layout_it = accesses_.find(&memlet);
4,420✔
188
    if (layout_it == accesses_.end()) {
4,420✔
189
        return nullptr;
2,240✔
190
    }
2,240✔
191
    return &layout_it->second;
2,180✔
192
}
4,420✔
193

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

203
    // Group all new accesses by container
204
    std::unordered_map<std::string, std::vector<const data_flow::Memlet*>> all_container_groups;
845✔
205
    for (auto& [memlet_ptr, acc] : accesses_) {
2,821✔
206
        if (before_set.find(memlet_ptr) != before_set.end()) {
2,821✔
207
            continue;
721✔
208
        }
721✔
209
        all_container_groups[acc.container].push_back(memlet_ptr);
2,100✔
210
    }
2,100✔
211

212
    // Sort memlets within each container group by element_id for deterministic processing order
213
    for (auto& [container, memlets] : all_container_groups) {
1,174✔
214
        std::sort(memlets.begin(), memlets.end(), [](const data_flow::Memlet* a, const data_flow::Memlet* b) {
1,987✔
215
            return a->element_id() < b->element_id();
1,987✔
216
        });
1,987✔
217
    }
1,174✔
218

219
    auto& assumptions_analysis = analysis_manager.get<AssumptionsAnalysis>();
845✔
220
    // Use trivial bounds (type-derived, e.g. unsigned >= 0) so symbolic min/max
221
    // over per-dimension index expressions can use parameter sign information.
222
    auto& assumptions = assumptions_analysis.get(loop.root(), /*include_trivial_bounds=*/true);
845✔
223
    // Start with SDFG-level parameters (read-only arguments like N, M)
224
    // then add any additional constant symbols from loop assumptions
225
    symbolic::SymbolSet parameters = assumptions_analysis.parameters();
845✔
226
    for (auto& entry : assumptions) {
7,761✔
227
        if (symbolic::eq(entry.first, loop.indvar())) {
7,761✔
228
            continue; // Skip induction variable itself
845✔
229
        }
845✔
230

231
        if (entry.second.constant()) {
6,916✔
232
            parameters.insert(entry.first);
2,414✔
233
        }
2,414✔
234
    }
6,916✔
235

236
    // Find direct child loops of this loop (not grandchildren)
237
    std::set<const structured_control_flow::StructuredLoop*> direct_child_loops;
845✔
238
    collect_direct_child_loops(loop.root(), direct_child_loops);
845✔
239

240
    for (auto& [container, memlets] : all_container_groups) {
1,174✔
241
        if (memlets.empty()) continue;
1,174✔
242

243
        // Find inner tiles from direct child loops only
244
        std::vector<const MemoryTile*> inner_tiles;
1,174✔
245
        for (auto& [key, tile] : tiles_) {
6,787✔
246
            if (tiles_before.count(key) > 0) continue;
6,787✔
247
            if (key.second != container) continue;
5,218✔
248
            if (direct_child_loops.count(key.first) == 0) continue;
1,627✔
249
            inner_tiles.push_back(&tile);
741✔
250
        }
741✔
251

252
        size_t ndims = 0;
1,174✔
253
        MemoryLayout reference_layout({symbolic::one()});
1,174✔
254
        // Separate min/max index lists to avoid unnecessary symbolic min/max
255
        std::vector<std::vector<symbolic::Expression>> min_indices;
1,174✔
256
        std::vector<std::vector<symbolic::Expression>> max_indices;
1,174✔
257

258
        if (!inner_tiles.empty()) {
1,174✔
259
            // Use inner tile min/max as representative values
260
            // Inner tiles have already resolved inner loop variables to their bounds
261
            ndims = inner_tiles[0]->min_subset.size();
680✔
262
            reference_layout = inner_tiles[0]->layout;
680✔
263
            min_indices.resize(ndims);
680✔
264
            max_indices.resize(ndims);
680✔
265

266
            for (const auto* tile : inner_tiles) {
741✔
267
                if (tile->min_subset.size() != ndims) continue;
741✔
268
                for (size_t d = 0; d < ndims; ++d) {
1,849✔
269
                    min_indices[d].push_back(tile->min_subset[d]);
1,108✔
270
                    max_indices[d].push_back(tile->max_subset[d]);
1,108✔
271
                }
1,108✔
272
            }
741✔
273

274
            // Propagate tile groups from child loops upward using the same
275
            // base-partitioning logic: group inner groups by their min_subset
276
            // base at this loop level, then merge each partition.
277
            std::vector<const MemoryTileGroup*> inner_groups;
680✔
278
            for (auto& [key, groups] : tile_groups_) {
5,593✔
279
                if (tiles_before.count({key.first, key.second}) > 0) continue;
5,593✔
280
                if (key.second != container) continue;
4,836✔
281
                if (direct_child_loops.count(key.first) == 0) continue;
1,627✔
282
                for (const auto& g : groups) {
818✔
283
                    inner_groups.push_back(&g);
818✔
284
                }
818✔
285
            }
741✔
286

287
            if (!inner_groups.empty()) {
680✔
288
                // Group inner groups by their base at this level
289
                struct OuterGroupEntry {
680✔
290
                    data_flow::Subset base;
680✔
291
                    std::vector<const MemoryTileGroup*> constituents;
680✔
292
                };
680✔
293
                std::vector<OuterGroupEntry> outer_partitions;
680✔
294

295
                for (const auto* ig : inner_groups) {
818✔
296
                    if (ig->tile.min_subset.size() != ndims) continue;
818✔
297

298
                    // Compute base: minimum of the inner group's min_subset per dim
299
                    data_flow::Subset base;
818✔
300
                    bool base_ok = true;
818✔
301
                    for (size_t d = 0; d < ndims; ++d) {
2,058✔
302
                        auto lb = symbolic::minimum(ig->tile.min_subset[d], parameters, assumptions, true);
1,240✔
303
                        if (lb.is_null()) {
1,240✔
UNCOV
304
                            lb = symbolic::minimum(ig->tile.min_subset[d], parameters, assumptions, false);
×
UNCOV
305
                        }
×
306
                        if (lb.is_null()) {
1,240✔
UNCOV
307
                            base_ok = false;
×
UNCOV
308
                            break;
×
UNCOV
309
                        }
×
310
                        base.push_back(symbolic::simplify(lb));
1,240✔
311
                    }
1,240✔
312
                    if (!base_ok) continue;
818✔
313

314
                    // Find matching partition (same base OR constant-offset base)
315
                    bool found = false;
818✔
316
                    for (auto& op : outer_partitions) {
818✔
317
                        bool const_diff = true;
193✔
318
                        for (size_t d = 0; d < ndims; ++d) {
328✔
319
                            auto diff = symbolic::simplify(symbolic::sub(base[d], op.base[d]));
242✔
320
                            if (!SymEngine::is_a<SymEngine::Integer>(*diff)) {
242✔
321
                                const_diff = false;
107✔
322
                                break;
107✔
323
                            }
107✔
324
                        }
242✔
325
                        if (const_diff) {
193✔
326
                            op.constituents.push_back(ig);
86✔
327
                            found = true;
86✔
328
                            break;
86✔
329
                        }
86✔
330
                    }
193✔
331
                    if (!found) {
818✔
332
                        outer_partitions.push_back({base, {ig}});
732✔
333
                    }
732✔
334
                }
818✔
335

336
                // For each partition, merge constituent tile bounds and collect memlets
337
                std::vector<MemoryTileGroup> result_groups;
680✔
338
                for (auto& op : outer_partitions) {
732✔
339
                    data_flow::Subset grp_min, grp_max;
732✔
340
                    bool grp_bounded = true;
732✔
341

342
                    for (size_t d = 0; d < ndims; ++d) {
1,850✔
343
                        symbolic::Expression d_min = SymEngine::null;
1,118✔
344
                        symbolic::Expression d_max = SymEngine::null;
1,118✔
345

346
                        for (const auto* c : op.constituents) {
1,240✔
347
                            // min from min_subset
348
                            auto lb = symbolic::minimum(c->tile.min_subset[d], parameters, assumptions, true);
1,240✔
349
                            if (lb.is_null())
1,240✔
UNCOV
350
                                lb = symbolic::minimum(c->tile.min_subset[d], parameters, assumptions, false);
×
351
                            if (lb.is_null()) {
1,240✔
UNCOV
352
                                grp_bounded = false;
×
UNCOV
353
                                break;
×
UNCOV
354
                            }
×
355
                            d_min = d_min.is_null() ? lb : symbolic::min(d_min, lb);
1,240✔
356

357
                            // max from max_subset
358
                            auto ub = symbolic::maximum(c->tile.max_subset[d], parameters, assumptions, true);
1,240✔
359
                            if (ub.is_null())
1,240✔
UNCOV
360
                                ub = symbolic::maximum(c->tile.max_subset[d], parameters, assumptions, false);
×
361
                            if (ub.is_null()) {
1,240✔
UNCOV
362
                                grp_bounded = false;
×
UNCOV
363
                                break;
×
364
                            }
×
365
                            d_max = d_max.is_null() ? ub : symbolic::max(d_max, ub);
1,240✔
366
                        }
1,240✔
367
                        if (!grp_bounded) break;
1,118✔
368
                        grp_min.push_back(symbolic::simplify(d_min));
1,118✔
369
                        grp_max.push_back(symbolic::simplify(d_max));
1,118✔
370
                    }
1,118✔
371
                    if (!grp_bounded) continue;
732✔
372

373
                    // Collect all memlets from constituent groups
374
                    std::vector<const data_flow::Memlet*> grp_memlets;
732✔
375
                    for (const auto* c : op.constituents) {
818✔
376
                        grp_memlets.insert(grp_memlets.end(), c->memlets.begin(), c->memlets.end());
818✔
377
                    }
818✔
378

379
                    MemoryTile grp_tile{container, grp_min, grp_max, reference_layout, true};
732✔
380
                    result_groups.push_back({grp_tile, std::move(grp_memlets)});
732✔
381
                }
732✔
382

383
                if (!result_groups.empty()) {
680✔
384
                    tile_groups_.insert({{&loop, container}, std::move(result_groups)});
680✔
385
                }
680✔
386
            }
680✔
387
        } else {
680✔
388
            // Use raw access indices (no inner tiles available)
389
            auto& first_access = accesses_.at(memlets[0]);
494✔
390
            auto& reference_shape = first_access.layout.shape();
494✔
391
            ndims = reference_shape.size();
494✔
392
            reference_layout = first_access.layout;
494✔
393
            min_indices.resize(ndims);
494✔
394
            max_indices.resize(ndims);
494✔
395

396
            bool consistent = true;
494✔
397
            for (const auto* memlet_ptr : memlets) {
738✔
398
                auto& acc = accesses_.at(memlet_ptr);
738✔
399
                auto& shape = acc.layout.shape();
738✔
400

401
                if (shape.size() != ndims) {
738✔
UNCOV
402
                    consistent = false;
×
UNCOV
403
                    break;
×
UNCOV
404
                }
×
405
                // Check inner dimensions match (all except first which may be unbounded)
406
                for (size_t d = 1; d < ndims; ++d) {
959✔
407
                    if (!symbolic::eq(shape[d], reference_shape[d])) {
221✔
UNCOV
408
                        consistent = false;
×
UNCOV
409
                        break;
×
UNCOV
410
                    }
×
411
                }
221✔
412
                if (!consistent) break;
738✔
413

414
                // Collect indices for each dimension
415
                if (acc.subset.size() != ndims) {
738✔
416
                    consistent = false;
46✔
417
                    break;
46✔
418
                }
46✔
419
                for (size_t d = 0; d < ndims; ++d) {
1,605✔
420
                    min_indices[d].push_back(acc.subset[d]);
913✔
421
                    max_indices[d].push_back(acc.subset[d]);
913✔
422
                }
913✔
423
            }
692✔
424

425
            if (!consistent) continue;
494✔
426

427
            // Compute tile groups for raw memlets
428
            compute_tile_groups(loop, container, memlets, reference_layout, ndims, parameters, assumptions);
448✔
429
        }
448✔
430

431
        if (ndims == 0) continue;
1,128✔
432

433
        // Compute min/max bounds for each dimension
434
        data_flow::Subset min_subset;
1,128✔
435
        data_flow::Subset max_subset;
1,128✔
436
        bool all_bounded = true;
1,128✔
437

438
        for (size_t d = 0; d < ndims; ++d) {
2,725✔
439
            symbolic::Expression dim_min = SymEngine::null;
1,601✔
440
            symbolic::Expression dim_max = SymEngine::null;
1,601✔
441

442
            // Compute dim_min from min_indices
443
            for (const auto& idx : min_indices[d]) {
2,021✔
444
                auto lb = symbolic::minimum(idx, parameters, assumptions, true);
2,021✔
445
                if (lb.is_null()) {
2,021✔
446
                    lb = symbolic::minimum(idx, parameters, assumptions, false);
8✔
447
                }
8✔
448
                if (lb.is_null()) {
2,021✔
UNCOV
449
                    all_bounded = false;
×
UNCOV
450
                    break;
×
UNCOV
451
                }
×
452
                if (dim_min.is_null()) {
2,021✔
453
                    dim_min = lb;
1,601✔
454
                } else {
1,601✔
455
                    dim_min = symbolic::min(dim_min, lb);
420✔
456
                }
420✔
457
            }
2,021✔
458
            if (!all_bounded) break;
1,601✔
459

460
            // Compute dim_max from max_indices
461
            for (const auto& idx : max_indices[d]) {
2,021✔
462
                auto ub = symbolic::maximum(idx, parameters, assumptions, true);
2,021✔
463
                if (ub.is_null()) {
2,021✔
464
                    ub = symbolic::maximum(idx, parameters, assumptions, false);
4✔
465
                }
4✔
466
                if (ub.is_null()) {
2,021✔
467
                    all_bounded = false;
4✔
468
                    break;
4✔
469
                }
4✔
470
                if (dim_max.is_null()) {
2,017✔
471
                    dim_max = ub;
1,597✔
472
                } else {
1,597✔
473
                    dim_max = symbolic::max(dim_max, ub);
420✔
474
                }
420✔
475
            }
2,017✔
476
            if (!all_bounded) break;
1,601✔
477

478
            min_subset.push_back(symbolic::simplify(dim_min));
1,597✔
479
            max_subset.push_back(symbolic::simplify(dim_max));
1,597✔
480
        }
1,597✔
481

482
        if (!all_bounded) continue;
1,128✔
483

484
        // Store this loop's tile with the original memory layout
485
        MemoryTile merged_tile{container, min_subset, max_subset, reference_layout, true};
1,124✔
486
        tiles_.insert({{&loop, container}, merged_tile});
1,124✔
487
    }
1,124✔
488
}
845✔
489

490
const MemoryTile* MemoryLayoutAnalysis::
491
    tile(const structured_control_flow::StructuredLoop& loop, const std::string& container) const {
26✔
492
    auto key = std::make_pair(&loop, container);
26✔
493
    auto it = tiles_.find(key);
26✔
494
    if (it == tiles_.end()) {
26✔
UNCOV
495
        return nullptr;
×
UNCOV
496
    }
×
497
    return &it->second;
26✔
498
}
26✔
499

500
void MemoryLayoutAnalysis::compute_tile_groups(
501
    structured_control_flow::StructuredLoop& loop,
502
    const std::string& container,
503
    const std::vector<const data_flow::Memlet*>& memlets,
504
    const MemoryLayout& reference_layout,
505
    size_t ndims,
506
    const symbolic::SymbolSet& parameters,
507
    const symbolic::Assumptions& assumptions
508
) {
448✔
509
    // For each memlet, compute per-dimension base (minimum of index expression)
510
    // Group memlets whose bases are symbolically equal in all dimensions
511
    struct GroupEntry {
448✔
512
        data_flow::Subset base; // per-dim minimum
448✔
513
        std::vector<const data_flow::Memlet*> group_memlets;
448✔
514
    };
448✔
515

516
    std::vector<GroupEntry> groups;
448✔
517

518
    for (const auto* memlet_ptr : memlets) {
692✔
519
        auto& acc = accesses_.at(memlet_ptr);
692✔
520
        if (acc.subset.size() != ndims) continue;
692✔
521

522
        // Compute per-dimension base (minimum)
523
        data_flow::Subset base;
692✔
524
        bool base_ok = true;
692✔
525
        for (size_t d = 0; d < ndims; ++d) {
1,605✔
526
            auto lb = symbolic::minimum(acc.subset[d], parameters, assumptions, true);
913✔
527
            if (lb.is_null()) {
913✔
528
                lb = symbolic::minimum(acc.subset[d], parameters, assumptions, false);
8✔
529
            }
8✔
530
            if (lb.is_null()) {
913✔
UNCOV
531
                base_ok = false;
×
UNCOV
532
                break;
×
UNCOV
533
            }
×
534
            base.push_back(symbolic::simplify(lb));
913✔
535
        }
913✔
536
        if (!base_ok) continue;
692✔
537

538
        // Find existing group with same base
539
        bool found = false;
692✔
540
        for (auto& group : groups) {
692✔
541
            if (group.base.size() != ndims) continue;
324✔
542
            bool match = true;
324✔
543
            for (size_t d = 0; d < ndims; ++d) {
575✔
544
                if (!symbolic::eq(group.base[d], base[d])) {
417✔
545
                    match = false;
166✔
546
                    break;
166✔
547
                }
166✔
548
            }
417✔
549
            if (match) {
324✔
550
                group.group_memlets.push_back(memlet_ptr);
158✔
551
                found = true;
158✔
552
                break;
158✔
553
            }
158✔
554
        }
324✔
555
        if (!found) {
692✔
556
            groups.push_back({base, {memlet_ptr}});
534✔
557
        }
534✔
558
    }
692✔
559

560
    if (groups.empty()) return;
448✔
561

562
    // Merge groups whose bases differ only by integer constants.
563
    // E.g. stencil bases [i-1, j], [i, j], [i+1, j] should merge (constant offsets in dim0).
564
    // But SYR2K bases [i, 0] vs [j, 0] should NOT merge (symbolic difference).
565
    std::vector<GroupEntry> merged_groups;
448✔
566
    for (auto& group : groups) {
534✔
567
        bool merged = false;
534✔
568
        for (auto& existing : merged_groups) {
534✔
569
            bool const_diff = true;
104✔
570
            for (size_t d = 0; d < ndims; ++d) {
187✔
571
                auto diff = symbolic::simplify(symbolic::sub(group.base[d], existing.base[d]));
135✔
572
                if (!SymEngine::is_a<SymEngine::Integer>(*diff)) {
135✔
573
                    const_diff = false;
52✔
574
                    break;
52✔
575
                }
52✔
576
            }
135✔
577
            if (const_diff) {
104✔
578
                existing.group_memlets
52✔
579
                    .insert(existing.group_memlets.end(), group.group_memlets.begin(), group.group_memlets.end());
52✔
580
                merged = true;
52✔
581
                break;
52✔
582
            }
52✔
583
        }
104✔
584
        if (!merged) {
534✔
585
            merged_groups.push_back(std::move(group));
482✔
586
        }
482✔
587
    }
534✔
588

589
    // Compute tile for each merged group
590
    std::vector<MemoryTileGroup> result_groups;
448✔
591
    for (auto& group : merged_groups) {
482✔
592
        std::vector<std::vector<symbolic::Expression>> min_indices(ndims);
482✔
593
        std::vector<std::vector<symbolic::Expression>> max_indices(ndims);
482✔
594

595
        for (const auto* memlet_ptr : group.group_memlets) {
692✔
596
            auto& acc = accesses_.at(memlet_ptr);
692✔
597
            for (size_t d = 0; d < ndims; ++d) {
1,605✔
598
                min_indices[d].push_back(acc.subset[d]);
913✔
599
                max_indices[d].push_back(acc.subset[d]);
913✔
600
            }
913✔
601
        }
692✔
602

603
        data_flow::Subset min_subset;
482✔
604
        data_flow::Subset max_subset;
482✔
605
        bool all_bounded = true;
482✔
606

607
        for (size_t d = 0; d < ndims; ++d) {
1,102✔
608
            symbolic::Expression dim_min = SymEngine::null;
624✔
609
            symbolic::Expression dim_max = SymEngine::null;
624✔
610

611
            for (const auto& idx : min_indices[d]) {
913✔
612
                auto lb = symbolic::minimum(idx, parameters, assumptions, true);
913✔
613
                if (lb.is_null()) {
913✔
614
                    lb = symbolic::minimum(idx, parameters, assumptions, false);
8✔
615
                }
8✔
616
                if (lb.is_null()) {
913✔
UNCOV
617
                    all_bounded = false;
×
UNCOV
618
                    break;
×
UNCOV
619
                }
×
620
                if (dim_min.is_null()) {
913✔
621
                    dim_min = lb;
624✔
622
                } else {
624✔
623
                    dim_min = symbolic::min(dim_min, lb);
289✔
624
                }
289✔
625
            }
913✔
626
            if (!all_bounded) break;
624✔
627

628
            for (const auto& idx : max_indices[d]) {
913✔
629
                auto ub = symbolic::maximum(idx, parameters, assumptions, true);
913✔
630
                if (ub.is_null()) {
913✔
631
                    ub = symbolic::maximum(idx, parameters, assumptions, false);
4✔
632
                }
4✔
633
                if (ub.is_null()) {
913✔
634
                    all_bounded = false;
4✔
635
                    break;
4✔
636
                }
4✔
637
                if (dim_max.is_null()) {
909✔
638
                    dim_max = ub;
620✔
639
                } else {
620✔
640
                    dim_max = symbolic::max(dim_max, ub);
289✔
641
                }
289✔
642
            }
909✔
643
            if (!all_bounded) break;
624✔
644

645
            min_subset.push_back(symbolic::simplify(dim_min));
620✔
646
            max_subset.push_back(symbolic::simplify(dim_max));
620✔
647
        }
620✔
648

649
        if (!all_bounded) continue;
482✔
650

651
        MemoryTile tile{container, min_subset, max_subset, reference_layout, true};
478✔
652
        result_groups.push_back({tile, group.group_memlets});
478✔
653
    }
478✔
654

655
    if (!result_groups.empty()) {
448✔
656
        tile_groups_.insert({{&loop, container}, std::move(result_groups)});
444✔
657
    }
444✔
658
}
448✔
659

660
const std::vector<MemoryTileGroup>* MemoryLayoutAnalysis::
661
    tile_groups(const structured_control_flow::StructuredLoop& loop, const std::string& container) const {
4✔
662
    auto key = std::make_pair(&loop, container);
4✔
663
    auto it = tile_groups_.find(key);
4✔
664
    if (it == tile_groups_.end()) {
4✔
UNCOV
665
        return nullptr;
×
UNCOV
666
    }
×
667
    return &it->second;
4✔
668
}
4✔
669

670
const MemoryTileGroup* MemoryLayoutAnalysis::
671
    tile_group_for(const structured_control_flow::StructuredLoop& loop, const data_flow::Memlet& memlet) const {
59✔
672
    // Find which container this memlet accesses
673
    auto acc_it = accesses_.find(&memlet);
59✔
674
    if (acc_it == accesses_.end()) {
59✔
675
        return nullptr;
1✔
676
    }
1✔
677
    auto& container = acc_it->second.container;
58✔
678

679
    auto key = std::make_pair(&loop, container);
58✔
680
    auto groups_it = tile_groups_.find(key);
58✔
681
    if (groups_it == tile_groups_.end()) {
58✔
UNCOV
682
        return nullptr;
×
UNCOV
683
    }
×
684

685
    for (const auto& group : groups_it->second) {
60✔
686
        for (const auto* m : group.memlets) {
70✔
687
            if (m == &memlet) {
70✔
688
                return &group;
58✔
689
            }
58✔
690
        }
70✔
691
    }
60✔
UNCOV
692
    return nullptr;
×
693
}
58✔
694

695
symbolic::MultiExpression MemoryTile::extents() const {
25✔
696
    symbolic::MultiExpression result;
25✔
697
    for (size_t d = 0; d < min_subset.size(); ++d) {
81✔
698
        result.push_back(symbolic::simplify(
56✔
699
            symbolic::expand(symbolic::add(symbolic::sub(max_subset[d], min_subset[d]), symbolic::one()))
56✔
700
        ));
56✔
701
    }
56✔
702
    return result;
25✔
703
}
25✔
704

705
symbolic::MultiExpression MemoryTile::extents_approx() const {
81✔
706
    symbolic::MultiExpression result;
81✔
707
    for (size_t d = 0; d < min_subset.size(); ++d) {
216✔
708
        result.push_back(symbolic::simplify(symbolic::expand(
135✔
709
            symbolic::overapproximate(symbolic::add(symbolic::sub(max_subset[d], min_subset[d]), symbolic::one()))
135✔
710
        )));
135✔
711
    }
135✔
712
    return result;
81✔
713
}
81✔
714

715
std::pair<symbolic::Expression, symbolic::Expression> MemoryTile::contiguous_range() const {
20✔
716
    auto& strides = layout.strides();
20✔
717
    auto first = layout.offset();
20✔
718
    auto last = layout.offset();
20✔
719
    for (size_t d = 0; d < min_subset.size(); ++d) {
66✔
720
        first = symbolic::add(first, symbolic::mul(strides[d], min_subset[d]));
46✔
721
        last = symbolic::add(last, symbolic::mul(strides[d], max_subset[d]));
46✔
722
    }
46✔
723
    return {symbolic::simplify(symbolic::expand(first)), symbolic::simplify(symbolic::expand(last))};
20✔
724
}
20✔
725

726
} // namespace analysis
727
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc