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

daisytuner / docc / 28529129791

01 Jul 2026 03:33PM UTC coverage: 62.123% (-0.007%) from 62.13%
28529129791

push

github

web-flow
Merge pull request #826 from daisytuner/adapt-cuda-search-space

Reject shared memory cpu pointer anywhere in a gpu kernel

12 of 22 new or added lines in 2 files covered. (54.55%)

3 existing lines in 1 file now uncovered.

39465 of 63527 relevant lines covered (62.12%)

978.67 hits per line

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

90.05
/opt/src/transformations/out_local_storage.cpp
1
#include "sdfg/transformations/out_local_storage.h"
2

3
#include <algorithm>
4
#include <cstddef>
5
#include <functional>
6
#include <string>
7

8
#include "sdfg/analysis/memory_layout_analysis.h"
9
#include "sdfg/analysis/users.h"
10
#include "sdfg/builder/structured_sdfg_builder.h"
11
#include "sdfg/data_flow/access_node.h"
12
#include "sdfg/data_flow/library_nodes/barrier_local_node.h"
13
#include "sdfg/data_flow/memlet.h"
14
#include "sdfg/passes/structured_control_flow/dead_cfg_elimination.h"
15
#include "sdfg/passes/structured_control_flow/sequence_fusion.h"
16
#include "sdfg/structured_control_flow/if_else.h"
17
#include "sdfg/structured_control_flow/sequence.h"
18
#include "sdfg/structured_control_flow/structured_loop.h"
19
#include "sdfg/symbolic/symbolic.h"
20
#include "sdfg/targets/gpu/gpu_schedule_type.h"
21
#include "sdfg/types/array.h"
22
#include "sdfg/types/pointer.h"
23
#include "sdfg/types/scalar.h"
24

25
namespace sdfg {
26
namespace transformations {
27

28
OutLocalStorage::OutLocalStorage(
29
    structured_control_flow::StructuredLoop& loop,
30
    const data_flow::AccessNode& access_node,
31
    const types::StorageType& storage_type
32
)
33
    : loop_(loop), access_node_(access_node), container_(access_node.data()), storage_type_(storage_type) {};
45✔
34

35
std::string OutLocalStorage::name() const { return "OutLocalStorage"; };
10✔
36

37
bool OutLocalStorage::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
41✔
38
    auto& sdfg = builder.subject();
41✔
39
    auto& body = this->loop_.root();
41✔
40

41
    tile_info_ = TileInfo{};
41✔
42

43
    // Criterion: Container must exist and is pointer
44
    if (!sdfg.exists(this->container_)) {
41✔
45
        return false;
×
46
    }
×
47
    auto& type = sdfg.type(this->container_);
41✔
48
    if (type.type_id() != types::TypeID::Pointer) {
41✔
49
        return false;
×
50
    }
×
51

52
    // Criterion: Container must be used in the loop body
53
    auto& users = analysis_manager.get<analysis::Users>();
41✔
54
    analysis::UsersView body_users(users, body);
41✔
55
    if (body_users.uses(this->container_).empty()) {
41✔
56
        return false;
2✔
57
    }
2✔
58

59
    // Criterion: Container must have writes (this is OutLocalStorage, not InLocalStorage)
60
    if (body_users.writes(this->container_).empty()) {
39✔
61
        return false;
1✔
62
    }
1✔
63

64
    // Criterion (GPU path): Loop must not be outermost (shared memory is per-block, not global)
65
    if (storage_type_.is_nv_shared()) {
38✔
66
        auto& loop_analysis = analysis_manager.get<analysis::LoopAnalysis>();
7✔
67
        if (loop_analysis.is_outermost_loop(&this->loop_)) {
7✔
68
            return false;
1✔
69
        }
1✔
70
    }
7✔
71

72
    // Determine if container is also read (read-write vs write-only)
73
    tile_info_.has_read = !body_users.reads(this->container_).empty();
37✔
74

75
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
37✔
76

77
    // Find a representative memlet from the access node to identify its group.
78
    // An access node may have multiple edges belonging to different tile groups.
79
    // We iterate all edges and select the first one whose tile group is valid
80
    // at the target loop level.
81
    const analysis::MemoryTileGroup* group = nullptr;
37✔
82
    auto& dfg = access_node_.get_parent();
37✔
83
    for (auto& memlet : dfg.in_edges(access_node_)) {
37✔
84
        auto* candidate = mla.tile_group_for(loop_, memlet);
30✔
85
        if (candidate) {
30✔
86
            group = candidate;
30✔
87
            break;
30✔
88
        }
30✔
89
    }
30✔
90
    if (!group) {
37✔
91
        for (auto& memlet : dfg.out_edges(access_node_)) {
7✔
92
            auto* candidate = mla.tile_group_for(loop_, memlet);
7✔
93
            if (candidate) {
7✔
94
                group = candidate;
7✔
95
                break;
7✔
96
            }
7✔
97
        }
7✔
98
    }
7✔
99
    if (!group) {
37✔
100
        return false;
×
101
    }
×
102

103
    auto& tile = group->tile;
37✔
104

105
    // Store group memlets for use in apply()
106
    group_memlets_.clear();
37✔
107
    group_memlets_.insert(group->memlets.begin(), group->memlets.end());
37✔
108

109
    // Get overapproximated extents (integer upper bounds)
110
    auto extents = tile.extents_approx();
37✔
111
    if (extents.empty()) {
37✔
112
        return false;
×
113
    }
×
114
    for (auto& ext : extents) {
62✔
115
        if (ext.is_null()) {
62✔
116
            return false;
×
117
        }
×
118
    }
62✔
119

120
    // Store tile info (before substitution, bases/strides stay symbolic)
121
    tile_info_.dimensions = extents;
37✔
122
    tile_info_.bases = tile.min_subset;
37✔
123
    tile_info_.strides = std::vector<symbolic::Expression>(tile.layout.strides().begin(), tile.layout.strides().end());
37✔
124
    tile_info_.offset = tile.layout.offset();
37✔
125

126
    // GPU shared memory: resolve symbolic extents using GPU block sizes and
127
    // require at least one cooperative dimension
128
    if (storage_type_.is_nv_shared()) {
37✔
129
        auto ancestors = ControlFlowNode::parent_chain(loop_);
6✔
130

131
        // Build substitution map: symbolic GPU map bounds → integer block sizes
132
        for (auto* node : ancestors) {
26✔
133
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
26✔
134
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
10✔
135
                    continue;
×
136
                }
×
137
                auto block_size = gpu::gpu_block_size(ancestor_map->schedule_type());
10✔
138
                // Extract symbolic bound from condition: Lt(indvar, BOUND)
139
                auto condition = ancestor_map->condition();
10✔
140
                if (SymEngine::is_a<SymEngine::StrictLessThan>(*condition)) {
10✔
141
                    auto stl = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(condition);
10✔
142
                    auto rhs = stl->get_args()[1];
10✔
143
                    auto iter_count = symbolic::sub(rhs, ancestor_map->init());
10✔
144
                    if (!SymEngine::is_a<SymEngine::Integer>(*iter_count)) {
10✔
145
                        // Symbolic bound — substitute with block size in extents and bases
146
                        for (auto& ext : tile_info_.dimensions) {
17✔
147
                            ext = symbolic::simplify(symbolic::subs(ext, iter_count, block_size));
17✔
148
                        }
17✔
149
                        for (auto& base : tile_info_.bases) {
17✔
150
                            base = symbolic::simplify(symbolic::subs(base, iter_count, block_size));
17✔
151
                        }
17✔
152
                    }
10✔
153
                }
10✔
154
            }
10✔
155
        }
26✔
156

157
        // Criterion: All extents must now be provably integer
158
        for (auto& ext : tile_info_.dimensions) {
10✔
159
            if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
10✔
160
                return false;
2✔
161
            }
2✔
162
        }
10✔
163

164
        // Criterion: At least one cooperative dimension
165
        bool has_cooperative_dim = false;
4✔
166
        for (auto* node : ancestors) {
12✔
167
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
12✔
168
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
6✔
169
                    continue;
×
170
                }
×
171
                bool appears_in_bases = false;
6✔
172
                for (auto& base : tile_info_.bases) {
9✔
173
                    if (symbolic::uses(base, ancestor_map->indvar())) {
9✔
174
                        appears_in_bases = true;
2✔
175
                        break;
2✔
176
                    }
2✔
177
                }
9✔
178
                if (!appears_in_bases) {
6✔
179
                    has_cooperative_dim = true;
4✔
180
                    break;
4✔
181
                }
4✔
182
            }
6✔
183
        }
12✔
184
        if (!has_cooperative_dim) {
4✔
185
            return false;
×
186
        }
×
187
    } else {
31✔
188
        // CPU path: All extents must be provably integer
189
        for (auto& ext : tile_info_.dimensions) {
52✔
190
            if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
52✔
191
                return false;
1✔
192
            }
1✔
193
        }
52✔
194

195
        // CPU_Stack must not be applied when the loop itself is a
196
        // GPU-scheduled map at the kernel boundary (no GPU-scheduled ancestors).
197
        // In that case the init/writeback copies would be placed on the host
198
        // while the compute runs on the device.
199
        if (auto* self_map = dynamic_cast<structured_control_flow::Map*>(&loop_)) {
30✔
200
            if (gpu::is_gpu_schedule(self_map->schedule_type())) {
4✔
201
                auto ancestors = ControlFlowNode::parent_chain(loop_);
2✔
202
                bool has_gpu_ancestor = false;
2✔
203
                for (auto* node : ancestors) {
4✔
204
                    if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
4✔
NEW
205
                        if (gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
×
NEW
206
                            has_gpu_ancestor = true;
×
NEW
207
                            break;
×
NEW
208
                        }
×
NEW
209
                    }
×
210
                }
4✔
211
                if (!has_gpu_ancestor) {
2✔
212
                    return false;
2✔
213
                }
2✔
214
            }
2✔
215
        }
4✔
216

217
        // Criterion: CPU_Stack inside a GPU region is only valid for per-thread
218
        // locals (all GPU map indvars appear in the tile bases). If there is a
219
        // cooperative dimension (a GPU indvar NOT in the bases), the buffer must
220
        // be NV_Shared so all threads in the block can see each other's writes.
221
        auto ancestors = ControlFlowNode::parent_chain(loop_);
28✔
222
        for (auto* node : ancestors) {
102✔
223
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
102✔
224
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
4✔
225
                    continue;
×
226
                }
×
227
                bool appears_in_bases = false;
4✔
228
                for (auto& base : tile_info_.bases) {
6✔
229
                    if (symbolic::uses(base, ancestor_map->indvar())) {
6✔
230
                        appears_in_bases = true;
4✔
231
                        break;
4✔
232
                    }
4✔
233
                }
6✔
234
                if (!appears_in_bases) {
4✔
235
                    // Cooperative dimension detected with CPU_Stack — invalid.
236
                    // The buffer requires NV_Shared for cross-thread visibility.
237
                    return false;
×
238
                }
×
239
            }
4✔
240
        }
102✔
241
    }
28✔
242

243
    return true;
32✔
244
}
37✔
245

246
void OutLocalStorage::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
28✔
247
    auto& sdfg = builder.subject();
28✔
248
    auto& users = analysis_manager.get<analysis::Users>();
28✔
249

250
    auto parent_node = loop_.get_parent();
28✔
251
    auto parent = dynamic_cast<structured_control_flow::Sequence*>(parent_node);
28✔
252
    if (!parent) {
28✔
253
        throw InvalidSDFGException("OutLocalStorage: Parent of loop must be a Sequence!");
×
254
    }
×
255

256
    // Get type information.
257
    auto* memlet = *group_memlets_.begin();
28✔
258
    types::Scalar scalar_type(memlet->base_type().primitive_type());
28✔
259
    types::Pointer pointer_type(scalar_type);
28✔
260

261
    // Create local buffer name
262
    local_name_ = builder.find_new_name("__daisy_out_local_storage_" + this->container_);
28✔
263

264

265
    // Collect varying dimensions (extent > 1) and their sizes.
266
    // Extent-1 dimensions are degenerate (no loop is needed) and must be
267
    // skipped when sizing the buffer, when creating copy indvars, and when
268
    // linearizing into the local buffer.  The bookkeeping must match what
269
    // `build_original_subset` expects (it indexes copy_indices by varying
270
    // dimension only).
271
    std::vector<size_t> varying_dims;
28✔
272
    std::vector<symbolic::Expression> varying_dim_sizes;
28✔
273
    for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
76✔
274
        auto& dim_size = tile_info_.dimensions.at(d);
48✔
275
        if (!symbolic::eq(dim_size, symbolic::integer(1))) {
48✔
276
            varying_dims.push_back(d);
30✔
277
            varying_dim_sizes.push_back(dim_size);
30✔
278
        }
30✔
279
    }
48✔
280

281
    // GPU classification: each ancestor GPU Map is either
282
    //  - per-thread (its Map indvar appears in tile.bases — each thread sees a
283
    //    distinct slice along that dim, so the shared buffer gets its own
284
    //    per-thread slot indexed by the within-block thread_idx), or
285
    //  - cooperative (Map indvar not in bases — all threads along that dim
286
    //    cooperatively load/store the same shared tile, strided by thread_idx).
287
    struct GpuDim {
28✔
288
        gpu::GPUDimension dim;
28✔
289
        symbolic::Symbol map_indvar; // global thread index (== thread_idx + blockIdx * blockDim)
28✔
290
        symbolic::Symbol thread_idx; // within-block thread index (NV_Symbol)
28✔
291
        symbolic::Integer block_size;
28✔
292
        bool is_per_thread;
28✔
293
    };
28✔
294
    std::vector<GpuDim> per_thread_dims; // populated only on GPU path
28✔
295
    std::vector<GpuDim> coop_dims; // populated only on GPU path
28✔
296
    bool is_rocm = false;
28✔
297

298
    if (storage_type_.is_nv_shared()) {
28✔
299
        auto ancestors = ControlFlowNode::parent_chain(loop_);
4✔
300
        for (auto* node : ancestors) {
20✔
301
            auto* m = dynamic_cast<structured_control_flow::Map*>(node);
20✔
302
            if (!m || !gpu::is_gpu_schedule(m->schedule_type())) continue;
20✔
303
            if (m->schedule_type().value() == "ROCM") {
8✔
304
                is_rocm = true;
×
305
                break;
×
306
            }
×
307
        }
8✔
308
        const std::string prefix = is_rocm ? "__daisy_hip_thread_idx_" : "__daisy_cuda_thread_idx_";
4✔
309
        auto suffix = [](gpu::GPUDimension d) -> std::string {
8✔
310
            switch (d) {
8✔
311
                case gpu::GPUDimension::X:
4✔
312
                    return "x";
4✔
313
                case gpu::GPUDimension::Y:
4✔
314
                    return "y";
4✔
315
                case gpu::GPUDimension::Z:
×
316
                    return "z";
×
317
            }
8✔
318
            return "?";
×
319
        };
8✔
320
        for (auto* node : ancestors) {
20✔
321
            auto* m = dynamic_cast<structured_control_flow::Map*>(node);
20✔
322
            if (!m || !gpu::is_gpu_schedule(m->schedule_type())) continue;
20✔
323
            GpuDim gd;
8✔
324
            gd.dim = gpu::gpu_dimension(m->schedule_type());
8✔
325
            gd.map_indvar = m->indvar();
8✔
326
            gd.thread_idx = symbolic::symbol(prefix + suffix(gd.dim));
8✔
327
            gd.block_size = gpu::gpu_block_size(m->schedule_type());
8✔
328
            gd.is_per_thread = false;
8✔
329
            for (auto& base : tile_info_.bases) {
11✔
330
                if (symbolic::uses(base, m->indvar())) {
11✔
331
                    gd.is_per_thread = true;
3✔
332
                    break;
3✔
333
                }
3✔
334
            }
11✔
335
            (gd.is_per_thread ? per_thread_dims : coop_dims).push_back(gd);
8✔
336
        }
8✔
337
        auto by_dim = [](const GpuDim& a, const GpuDim& b) {
4✔
338
            return static_cast<int>(a.dim) < static_cast<int>(b.dim);
1✔
339
        };
1✔
340
        std::sort(per_thread_dims.begin(), per_thread_dims.end(), by_dim);
4✔
341
        std::sort(coop_dims.begin(), coop_dims.end(), by_dim);
4✔
342

343
        // Ensure within-block thread_idx containers exist. Codegen recognises
344
        // NV_Symbol-typed scalars and substitutes them with threadIdx.{x,y,z}
345
        // (CUDA) or the ROCm equivalent at emission time.
346
        auto ensure_idx = [&](const symbolic::Symbol& sym) {
8✔
347
            if (!sdfg.exists(sym->get_name())) {
8✔
348
                types::Scalar idx_type(types::PrimitiveType::Int32);
8✔
349
                idx_type.storage_type(types::StorageType::NV_Symbol());
8✔
350
                builder.add_container(sym->get_name(), idx_type);
8✔
351
            }
8✔
352
        };
8✔
353
        for (auto& gd : per_thread_dims) ensure_idx(gd.thread_idx);
4✔
354
        for (auto& gd : coop_dims) ensure_idx(gd.thread_idx);
5✔
355
    }
4✔
356

357
    // Buffer dim sizes: [per-thread block sizes (X, Y, Z canonical order)] ++
358
    //                   [varying tile dim sizes (original access-dim order)]
359
    std::vector<symbolic::Expression> buf_dim_sizes;
28✔
360
    for (auto& gd : per_thread_dims) buf_dim_sizes.push_back(gd.block_size);
28✔
361
    for (auto& s : varying_dim_sizes) buf_dim_sizes.push_back(s);
30✔
362

363
    // Total buffer size (number of scalar slots)
364
    symbolic::Expression total_size = symbolic::integer(1);
28✔
365
    for (auto& s : buf_dim_sizes) total_size = symbolic::mul(total_size, s);
33✔
366

367
    // Per-thread index prefix (each thread's fixed buffer coords)
368
    std::vector<symbolic::Expression> per_thread_indices;
28✔
369
    for (auto& gd : per_thread_dims) per_thread_indices.push_back(gd.thread_idx);
28✔
370

371
    // Create the local buffer with specified storage type
372
    types::Array buffer_type(storage_type_, 0, {}, scalar_type, total_size);
28✔
373
    builder.add_container(local_name_, buffer_type);
28✔
374

375
    // Row-major linearization over buf_dim_sizes (leftmost dim = outermost stride)
376
    auto linearize_exprs = [&](const std::vector<symbolic::Expression>& indices) -> symbolic::Expression {
86✔
377
        symbolic::Expression linear_idx = symbolic::integer(0);
86✔
378
        symbolic::Expression stride = symbolic::integer(1);
86✔
379
        for (int i = static_cast<int>(indices.size()) - 1; i >= 0; i--) {
188✔
380
            linear_idx = symbolic::add(linear_idx, symbolic::mul(indices[i], stride));
102✔
381
            stride = symbolic::mul(stride, buf_dim_sizes[i]);
102✔
382
        }
102✔
383
        return linear_idx;
86✔
384
    };
86✔
385

386
    // Helper: build linearized local index from per-dimension indvars (symbols)
387
    auto linearize = [&](const std::vector<symbolic::Symbol>& indvars) -> symbolic::Expression {
38✔
388
        std::vector<symbolic::Expression> exprs(indvars.begin(), indvars.end());
38✔
389
        return linearize_exprs(exprs);
38✔
390
    };
38✔
391

392
    // Helper: build source subset (base[d] + copy_indvar[d]) for original container
393
    auto build_original_subset = [&](const std::vector<symbolic::Expression>& copy_indices) -> data_flow::Subset {
43✔
394
        std::vector<symbolic::Expression> full_indices;
43✔
395
        size_t var_idx = 0;
43✔
396
        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
117✔
397
            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
74✔
398
                full_indices.push_back(symbolic::add(tile_info_.bases.at(d), copy_indices.at(var_idx++)));
47✔
399
            } else {
47✔
400
                full_indices.push_back(tile_info_.bases.at(d));
27✔
401
            }
27✔
402
        }
74✔
403

404
        symbolic::Expression linear = tile_info_.offset;
43✔
405
        for (size_t d = 0; d < full_indices.size(); d++) {
117✔
406
            linear = symbolic::add(linear, symbolic::mul(tile_info_.strides.at(d), full_indices.at(d)));
74✔
407
        }
74✔
408
        return {linear};
43✔
409
    };
43✔
410

411
    if (storage_type_.is_nv_shared()) {
28✔
412
        // ============================================================
413
        // GPU COOPERATIVE PATH
414
        // ============================================================
415
        // Each thread owns a fixed slot along per-thread buffer dims and
416
        // strides through the varying-flat range with the other threads
417
        // sharing that slot (i.e. threads in cooperative dims only).
418

419
        // Total cooperative-thread count (= 1 if no cooperative dims)
420
        symbolic::Expression total_coop_threads = symbolic::integer(1);
4✔
421
        for (auto& cd : coop_dims) {
5✔
422
            total_coop_threads = symbolic::mul(total_coop_threads, cd.block_size);
5✔
423
        }
5✔
424

425
        // Flat within-block index over cooperative dims only (= 0 if none).
426
        // Row-major: X is least-significant when present.
427
        symbolic::Expression coop_flat = symbolic::integer(0);
4✔
428
        {
4✔
429
            symbolic::Expression stride = symbolic::integer(1);
4✔
430
            for (auto it = coop_dims.rbegin(); it != coop_dims.rend(); ++it) {
9✔
431
                coop_flat = symbolic::add(coop_flat, symbolic::mul(it->thread_idx, stride));
5✔
432
                stride = symbolic::mul(stride, it->block_size);
5✔
433
            }
5✔
434
        }
4✔
435

436
        // Varying-flat size = product of tile dim extents (excluding extent==1).
437
        // This is the address range each thread cooperatively walks within its
438
        // per-thread slot.
439
        symbolic::Expression varying_flat_size = symbolic::integer(1);
4✔
440
        for (auto& s : varying_dim_sizes) {
4✔
441
            varying_flat_size = symbolic::mul(varying_flat_size, s);
4✔
442
        }
4✔
443

444
        // Helper to decompose a flat varying index into per-varying-dim indices
445
        // (row-major), and to build the buffer dest subset (per_thread ++ varying).
446
        auto decompose = [&](const symbolic::Symbol& idx_var) {
5✔
447
            std::vector<symbolic::Expression> result;
5✔
448
            symbolic::Expression remainder = idx_var;
5✔
449
            for (size_t i = 0; i < varying_dim_sizes.size(); i++) {
10✔
450
                if (i + 1 < varying_dim_sizes.size()) {
5✔
451
                    symbolic::Expression divisor = symbolic::integer(1);
×
452
                    for (size_t j = i + 1; j < varying_dim_sizes.size(); j++) {
×
453
                        divisor = symbolic::mul(divisor, varying_dim_sizes[j]);
×
454
                    }
×
455
                    result.push_back(symbolic::div(remainder, divisor));
×
456
                    remainder = symbolic::mod(remainder, divisor);
×
457
                } else {
5✔
458
                    result.push_back(remainder);
5✔
459
                }
5✔
460
            }
5✔
461
            return result;
5✔
462
        };
5✔
463
        auto buf_subset_for = [&](const std::vector<symbolic::Expression>& varying_decomp) -> data_flow::Subset {
5✔
464
            std::vector<symbolic::Expression> dest_indices = per_thread_indices;
5✔
465
            for (auto& v : varying_decomp) dest_indices.push_back(v);
5✔
466
            return {linearize_exprs(dest_indices)};
5✔
467
        };
5✔
468

469
        // INIT: barrier → cooperative copy-in → barrier (if has_read)
470
        if (tile_info_.has_read) {
4✔
471
            // Barrier before init
472
            auto& barrier_block1 = builder.add_block_before(*parent, loop_, {}, loop_.debug_info());
1✔
473
            builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block1, {});
1✔
474

475
            // Cooperative copy-in loop
476
            auto idx_name = builder.find_new_name("__daisy_ols_coop_init_" + this->container_);
1✔
477
            types::Scalar idx_type(types::PrimitiveType::UInt64);
1✔
478
            builder.add_container(idx_name, idx_type);
1✔
479
            auto idx_var = symbolic::symbol(idx_name);
1✔
480

481
            auto& init_loop = builder.add_map_before(
1✔
482
                *parent,
1✔
483
                loop_,
1✔
484
                idx_var,
1✔
485
                symbolic::Lt(idx_var, varying_flat_size),
1✔
486
                coop_flat,
1✔
487
                symbolic::add(idx_var, total_coop_threads),
1✔
488
                structured_control_flow::ScheduleType_Sequential::create(),
1✔
489
                {},
1✔
490
                loop_.debug_info()
1✔
491
            );
1✔
492

493
            auto& init_block = builder.add_block(init_loop.root());
1✔
494
            auto& init_src = builder.add_access(init_block, this->container_);
1✔
495
            auto& init_dst = builder.add_access(init_block, local_name_);
1✔
496
            auto& init_tasklet = builder.add_tasklet(init_block, data_flow::TaskletCode::assign, "_out", {"_in"});
1✔
497

498
            auto init_decomp = decompose(idx_var);
1✔
499
            auto init_src_subset = build_original_subset(init_decomp);
1✔
500
            auto init_dst_subset = buf_subset_for(init_decomp);
1✔
501
            builder.add_computational_memlet(init_block, init_src, init_tasklet, "_in", init_src_subset, pointer_type);
1✔
502
            builder.add_computational_memlet(init_block, init_tasklet, "_out", init_dst, init_dst_subset, buffer_type);
1✔
503

504
            // Barrier after init
505
            auto& barrier_block2 = builder.add_block_before(*parent, loop_, {}, loop_.debug_info());
1✔
506
            builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block2, {});
1✔
507
        }
1✔
508

509
        // WRITEBACK: barrier → cooperative copy-out → barrier
510
        {
4✔
511
            // Barrier before writeback
512
            auto& barrier_block3 = builder.add_block_after(*parent, loop_, {}, loop_.debug_info());
4✔
513
            builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block3, {});
4✔
514

515
            // Cooperative writeback loop
516
            auto idx_name = builder.find_new_name("__daisy_ols_coop_wb_" + this->container_);
4✔
517
            types::Scalar idx_type(types::PrimitiveType::UInt64);
4✔
518
            builder.add_container(idx_name, idx_type);
4✔
519
            auto idx_var = symbolic::symbol(idx_name);
4✔
520

521
            auto& wb_loop = builder.add_map_after(
4✔
522
                *parent,
4✔
523
                loop_,
4✔
524
                idx_var,
4✔
525
                symbolic::Lt(idx_var, varying_flat_size),
4✔
526
                coop_flat,
4✔
527
                symbolic::add(idx_var, total_coop_threads),
4✔
528
                structured_control_flow::ScheduleType_Sequential::create(),
4✔
529
                {},
4✔
530
                loop_.debug_info()
4✔
531
            );
4✔
532

533
            auto& wb_block = builder.add_block(wb_loop.root());
4✔
534
            auto& wb_src = builder.add_access(wb_block, local_name_);
4✔
535
            auto& wb_dst = builder.add_access(wb_block, this->container_);
4✔
536
            auto& wb_tasklet = builder.add_tasklet(wb_block, data_flow::TaskletCode::assign, "_out", {"_in"});
4✔
537

538
            auto wb_decomp = decompose(idx_var);
4✔
539
            auto wb_src_subset = buf_subset_for(wb_decomp);
4✔
540
            auto wb_dst_subset = build_original_subset(wb_decomp);
4✔
541
            builder.add_computational_memlet(wb_block, wb_src, wb_tasklet, "_in", wb_src_subset, buffer_type);
4✔
542
            builder.add_computational_memlet(wb_block, wb_tasklet, "_out", wb_dst, wb_dst_subset, pointer_type);
4✔
543

544
            // Barrier after writeback
545
            auto& barrier_block4 = builder.add_block_after(*parent, loop_, {}, loop_.debug_info());
4✔
546
            builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block4, {});
4✔
547
        }
4✔
548
    } else {
24✔
549
        // ============================================================
550
        // CPU SEQUENTIAL PATH
551
        // ============================================================
552
        if (tile_info_.has_read) {
24✔
553
            std::vector<symbolic::Symbol> init_indvars;
14✔
554
            structured_control_flow::Sequence* init_scope =
14✔
555
                &builder.add_sequence_before(*parent, loop_, {}, loop_.debug_info());
14✔
556
            for (size_t i = 0; i < varying_dims.size(); i++) {
30✔
557
                size_t d = varying_dims[i];
16✔
558
                auto indvar_name =
16✔
559
                    builder.find_new_name("__daisy_ols_init_" + this->container_ + "_d" + std::to_string(d));
16✔
560
                types::Scalar indvar_type(types::PrimitiveType::UInt64);
16✔
561
                builder.add_container(indvar_name, indvar_type);
16✔
562
                auto indvar = symbolic::symbol(indvar_name);
16✔
563
                init_indvars.push_back(indvar);
16✔
564

565
                auto init = symbolic::integer(0);
16✔
566
                auto condition = symbolic::Lt(indvar, varying_dim_sizes[i]);
16✔
567
                auto update = symbolic::add(indvar, symbolic::integer(1));
16✔
568

569
                auto& init_loop = builder.add_map(
16✔
570
                    *init_scope,
16✔
571
                    indvar,
16✔
572
                    condition,
16✔
573
                    init,
16✔
574
                    update,
16✔
575
                    structured_control_flow::ScheduleType_Sequential::create(),
16✔
576
                    {},
16✔
577
                    loop_.debug_info()
16✔
578
                );
16✔
579
                init_scope = &init_loop.root();
16✔
580
            }
16✔
581

582
            // Create init copy block
583
            auto& init_block = builder.add_block(*init_scope);
14✔
584
            auto& init_src = builder.add_access(init_block, this->container_);
14✔
585
            auto& init_dst = builder.add_access(init_block, local_name_);
14✔
586
            auto& init_tasklet = builder.add_tasklet(init_block, data_flow::TaskletCode::assign, "_out", {"_in"});
14✔
587

588
            std::vector<symbolic::Expression> init_exprs(init_indvars.begin(), init_indvars.end());
14✔
589
            auto init_src_subset = build_original_subset(init_exprs);
14✔
590
            data_flow::Subset init_dst_subset = {linearize(init_indvars)};
14✔
591

592
            builder.add_computational_memlet(init_block, init_src, init_tasklet, "_in", init_src_subset, pointer_type);
14✔
593
            builder.add_computational_memlet(init_block, init_tasklet, "_out", init_dst, init_dst_subset, buffer_type);
14✔
594
        }
14✔
595

596
        // Writeback Maps
597
        {
24✔
598
            std::vector<symbolic::Symbol> wb_indvars;
24✔
599
            structured_control_flow::Sequence* wb_scope =
24✔
600
                &builder.add_sequence_after(*parent, loop_, {}, loop_.debug_info());
24✔
601
            for (size_t i = 0; i < varying_dims.size(); i++) {
50✔
602
                size_t d = varying_dims[i];
26✔
603
                auto indvar_name =
26✔
604
                    builder.find_new_name("__daisy_ols_wb_" + this->container_ + "_d" + std::to_string(d));
26✔
605
                types::Scalar indvar_type(types::PrimitiveType::UInt64);
26✔
606
                builder.add_container(indvar_name, indvar_type);
26✔
607
                auto indvar = symbolic::symbol(indvar_name);
26✔
608
                wb_indvars.push_back(indvar);
26✔
609

610
                auto init = symbolic::integer(0);
26✔
611
                auto condition = symbolic::Lt(indvar, varying_dim_sizes[i]);
26✔
612
                auto update = symbolic::add(indvar, symbolic::integer(1));
26✔
613

614
                auto& wb_loop = builder.add_map(
26✔
615
                    *wb_scope,
26✔
616
                    indvar,
26✔
617
                    condition,
26✔
618
                    init,
26✔
619
                    update,
26✔
620
                    structured_control_flow::ScheduleType_Sequential::create(),
26✔
621
                    {},
26✔
622
                    loop_.debug_info()
26✔
623
                );
26✔
624
                wb_scope = &wb_loop.root();
26✔
625
            }
26✔
626

627
            // Create writeback copy block
628
            auto& wb_block = builder.add_block(*wb_scope);
24✔
629
            auto& wb_src = builder.add_access(wb_block, local_name_);
24✔
630
            auto& wb_dst = builder.add_access(wb_block, this->container_);
24✔
631
            auto& wb_tasklet = builder.add_tasklet(wb_block, data_flow::TaskletCode::assign, "_out", {"_in"});
24✔
632

633
            std::vector<symbolic::Expression> wb_exprs(wb_indvars.begin(), wb_indvars.end());
24✔
634
            data_flow::Subset wb_src_subset = {linearize(wb_indvars)};
24✔
635
            auto wb_dst_subset = build_original_subset(wb_exprs);
24✔
636

637
            builder.add_computational_memlet(wb_block, wb_src, wb_tasklet, "_in", wb_src_subset, buffer_type);
24✔
638
            builder.add_computational_memlet(wb_block, wb_tasklet, "_out", wb_dst, wb_dst_subset, pointer_type);
24✔
639
        }
24✔
640
    }
24✔
641

642
    // ==================================================================
643
    // Update accesses in the main loop to use the local buffer
644
    // ==================================================================
645
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
28✔
646

647
    // Recursive helper to traverse all blocks in the loop body
648
    std::function<void(structured_control_flow::ControlFlowNode&)> rewrite_accesses;
28✔
649
    rewrite_accesses = [&](structured_control_flow::ControlFlowNode& node) {
92✔
650
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&node)) {
92✔
651
            auto& dfg = block->dataflow();
40✔
652

653
            // Collect access nodes to process (avoid iterator invalidation when splitting)
654
            std::vector<data_flow::AccessNode*> access_nodes;
40✔
655
            for (auto* access_node : dfg.data_nodes()) {
96✔
656
                if (access_node->data() == this->container_) {
96✔
657
                    access_nodes.push_back(access_node);
46✔
658
                }
46✔
659
            }
96✔
660

661
            for (auto* access : access_nodes) {
46✔
662
                // Classify memlets: group vs non-group
663
                struct MemletRewrite {
46✔
664
                    data_flow::Memlet* memlet;
46✔
665
                    data_flow::Subset local_subset;
46✔
666
                    bool is_outgoing;
46✔
667
                };
46✔
668
                std::vector<MemletRewrite> group_rewrites;
46✔
669
                bool all_in_group = true;
46✔
670

671
                // Outgoing memlets (reads from this access node)
672
                for (auto& memlet : dfg.out_edges(*access)) {
46✔
673
                    if (group_memlets_.count(&memlet) == 0) {
16✔
674
                        all_in_group = false;
1✔
675
                        continue;
1✔
676
                    }
1✔
677
                    auto* acc = mla.access(memlet);
15✔
678
                    if (acc && acc->subset.size() == tile_info_.dimensions.size()) {
15✔
679
                        // Buffer index: [per-thread thread_idx (X,Y,Z order)] ++ [varying d: subset[d] - base[d]]
680
                        std::vector<symbolic::Expression> local_indices = per_thread_indices;
15✔
681
                        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
41✔
682
                            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
26✔
683
                                local_indices.push_back(symbolic::sub(acc->subset.at(d), tile_info_.bases.at(d)));
17✔
684
                            }
17✔
685
                        }
26✔
686
                        symbolic::Expression linear_idx = linearize_exprs(local_indices);
15✔
687
                        group_rewrites.push_back({&memlet, {linear_idx}, true});
15✔
688
                    } else {
15✔
689
                        // Memlet is claimed by the group but we cannot rewrite it (no
690
                        // delinearized access info). Leaving it as the original container
691
                        // would create a half-renamed access node. Bail out of renaming
692
                        // to keep the SDFG consistent.
693
                        all_in_group = false;
×
694
                    }
×
695
                }
15✔
696
                // Incoming memlets (writes to this access node)
697
                for (auto& memlet : dfg.in_edges(*access)) {
46✔
698
                    if (group_memlets_.count(&memlet) == 0) {
31✔
699
                        all_in_group = false;
3✔
700
                        continue;
3✔
701
                    }
3✔
702
                    auto* acc = mla.access(memlet);
28✔
703
                    if (acc && acc->subset.size() == tile_info_.dimensions.size()) {
28✔
704
                        // Buffer index: [per-thread thread_idx (X,Y,Z order)] ++ [varying d: subset[d] - base[d]]
705
                        std::vector<symbolic::Expression> local_indices = per_thread_indices;
28✔
706
                        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
76✔
707
                            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
48✔
708
                                local_indices.push_back(symbolic::sub(acc->subset.at(d), tile_info_.bases.at(d)));
30✔
709
                            }
30✔
710
                        }
48✔
711
                        symbolic::Expression linear_idx = linearize_exprs(local_indices);
28✔
712
                        group_rewrites.push_back({&memlet, {linear_idx}, false});
28✔
713
                    } else {
28✔
714
                        all_in_group = false;
×
715
                    }
×
716
                }
28✔
717

718
                if (group_rewrites.empty()) continue;
46✔
719

720
                if (all_in_group) {
43✔
721
                    // Simple case: all memlets in group → rewrite in-place and rename
722
                    for (auto& rw : group_rewrites) {
42✔
723
                        rw.memlet->set_subset(rw.local_subset);
42✔
724
                        rw.memlet->set_base_type(buffer_type);
42✔
725
                    }
42✔
726
                    access->data(local_name_);
42✔
727
                } else {
42✔
728
                    // Mixed case: split — create new local access node, redirect group memlets
729
                    auto& local_access = builder.add_access(*block, local_name_);
1✔
730
                    for (auto& rw : group_rewrites) {
1✔
731
                        if (rw.is_outgoing) {
1✔
732
                            // outgoing: access→tasklet  →  local_access→tasklet
733
                            auto& dst_node = rw.memlet->dst();
×
734
                            auto dst_conn = rw.memlet->dst_conn();
×
735
                            builder.remove_memlet(*block, *rw.memlet);
×
736
                            builder.add_memlet(
×
737
                                *block, local_access, "void", dst_node, dst_conn, rw.local_subset, buffer_type, {}
×
738
                            );
×
739
                        } else {
1✔
740
                            // incoming: tasklet→access  →  tasklet→local_access
741
                            auto& src_node = rw.memlet->src();
1✔
742
                            auto src_conn = rw.memlet->src_conn();
1✔
743
                            builder.remove_memlet(*block, *rw.memlet);
1✔
744
                            builder.add_memlet(
1✔
745
                                *block, src_node, src_conn, local_access, "void", rw.local_subset, buffer_type, {}
1✔
746
                            );
1✔
747
                        }
1✔
748
                    }
1✔
749
                }
1✔
750
            }
43✔
751
        } else if (auto* seq = dynamic_cast<structured_control_flow::Sequence*>(&node)) {
52✔
752
            for (size_t i = 0; i < seq->size(); i++) {
92✔
753
                rewrite_accesses(seq->at(i).first);
52✔
754
            }
52✔
755
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&node)) {
40✔
756
            rewrite_accesses(loop->root());
12✔
757
        } else if (auto* if_else = dynamic_cast<structured_control_flow::IfElse*>(&node)) {
12✔
758
            for (size_t i = 0; i < if_else->size(); i++) {
×
759
                rewrite_accesses(if_else->at(i).first);
×
760
            }
×
761
        }
×
762
    };
92✔
763
    rewrite_accesses(loop_.root());
28✔
764

765
    // Cleanup
766
    analysis_manager.invalidate_all();
28✔
767

768
    passes::SequenceFusion sf_pass;
28✔
769
    passes::DeadCFGElimination dce_pass;
28✔
770
    bool applies = false;
28✔
771
    do {
52✔
772
        applies = false;
52✔
773
        applies |= dce_pass.run(builder, analysis_manager);
52✔
774
        applies |= sf_pass.run(builder, analysis_manager);
52✔
775
    } while (applies);
52✔
776
};
28✔
777

778
void OutLocalStorage::to_json(nlohmann::json& j) const {
5✔
779
    j["transformation_type"] = this->name();
5✔
780
    j["parameters"] = nlohmann::json::object();
5✔
781

782
    serializer::JSONSerializer serializer_full;
5✔
783
    j["parameters"]["storage_type"] = nlohmann::json::object();
5✔
784
    serializer_full.storage_type_to_json(j["parameters"]["storage_type"], storage_type_);
5✔
785

786
    serializer::JSONSerializer ser_flat(false);
5✔
787
    j["subgraph"] = nlohmann::json::object();
5✔
788
    j["subgraph"]["0"] = nlohmann::json::object();
5✔
789
    ser_flat.serialize_node(j["subgraph"]["0"], loop_);
5✔
790

791
    j["subgraph"]["1"] = nlohmann::json::object();
5✔
792
    j["subgraph"]["1"]["element_id"] = access_node_.element_id();
5✔
793
    j["subgraph"]["1"]["type"] = "access_node";
5✔
794
};
5✔
795

796
OutLocalStorage OutLocalStorage::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
3✔
797
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
3✔
798
    auto element = builder.find_element_by_id(loop_id);
3✔
799
    if (!element) {
3✔
800
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
801
    }
×
802
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
3✔
803

804
    auto access_node = dynamic_cast<
3✔
805
        data_flow::AccessNode*>(builder.find_element_by_id(desc.at("subgraph").at("1").at("element_id").get<size_t>()));
3✔
806
    if (!access_node) {
3✔
807
        throw InvalidTransformationDescriptionException(
×
808
            "Access node with ID " + std::to_string(desc.at("subgraph").at("1").at("element_id").get<size_t>()) +
×
809
            " not found."
×
810
        );
×
811
    }
×
812

813
    types::StorageType storage_type = types::StorageType::CPU_Stack();
3✔
814
    if (desc["parameters"].contains("storage_type")) {
3✔
815
        serializer::JSONSerializer ser;
3✔
816
        storage_type = ser.json_to_storage_type(desc.at("parameters").at("storage_type"));
3✔
817
    }
3✔
818

819
    return OutLocalStorage(*loop, *access_node, storage_type);
3✔
820
};
3✔
821

822
} // namespace transformations
823
} // 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