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

daisytuner / docc / 28576074556

02 Jul 2026 08:23AM UTC coverage: 62.141% (+0.02%) from 62.123%
28576074556

push

github

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

Guard application to for loop in gpu kernel

36 of 40 new or added lines in 2 files covered. (90.0%)

3 existing lines in 2 files now uncovered.

39485 of 63541 relevant lines covered (62.14%)

979.01 hits per line

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

84.47
/opt/src/transformations/in_local_storage.cpp
1
#include "sdfg/transformations/in_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
InLocalStorage::InLocalStorage(
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) {}
44✔
34

35
std::string InLocalStorage::name() const { return "InLocalStorage"; }
13✔
36

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

41
    tile_info_ = TileInfo{};
40✔
42

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

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

59
    // Criterion: Container must be read-only within the loop (no writes)
60
    if (!body_users.writes(this->container_).empty()) {
38✔
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()) {
37✔
66
        auto& loop_analysis = analysis_manager.get<analysis::LoopAnalysis>();
8✔
67
        if (loop_analysis.is_outermost_loop(&this->loop_)) {
8✔
68
            return false;
1✔
69
        }
1✔
70
    }
8✔
71

72
    // Use MemoryLayoutAnalysis tile group API
73
    // Find a representative memlet from the access node to identify its group.
74
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
36✔
75
    const analysis::MemoryTileGroup* group = nullptr;
36✔
76
    auto& dfg = access_node_.get_parent();
36✔
77
    for (auto& memlet : dfg.out_edges(access_node_)) {
36✔
78
        auto* candidate = mla.tile_group_for(loop_, memlet);
36✔
79
        if (!candidate) {
36✔
80
            continue;
×
81
        }
×
82

83
        auto extents = candidate->tile.extents_approx();
36✔
84
        if (extents.empty()) {
36✔
85
            continue;
×
86
        }
×
87

88
        // Reject candidates with any unbounded-dependent extent (returned as null).
89
        bool has_null = false;
36✔
90
        for (auto& ext : extents) {
64✔
91
            if (ext.is_null()) {
64✔
92
                has_null = true;
×
93
                break;
×
94
            }
×
95
        }
64✔
96
        if (has_null) {
36✔
97
            continue;
×
98
        }
×
99

100
        // GPU path: accept first valid group (substitution happens later)
101
        if (storage_type_.is_nv_shared()) {
36✔
102
            group = candidate;
7✔
103
            break;
7✔
104
        }
7✔
105

106
        // CPU path: require provably integer extents
107
        bool all_integer = true;
29✔
108
        for (auto& ext : extents) {
51✔
109
            if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
51✔
110
                all_integer = false;
1✔
111
                break;
1✔
112
            }
1✔
113
        }
51✔
114
        if (all_integer) {
29✔
115
            group = candidate;
28✔
116
            break;
28✔
117
        }
28✔
118
    }
29✔
119
    if (!group) {
36✔
120
        return false;
1✔
121
    }
1✔
122

123
    auto& tile = group->tile;
35✔
124
    auto extents = tile.extents_approx();
35✔
125

126
    // Store group memlets for use in apply()
127
    group_memlets_.clear();
35✔
128
    group_memlets_.insert(group->memlets.begin(), group->memlets.end());
35✔
129

130
    // Store tile info (before substitution, bases/strides stay symbolic)
131
    tile_info_.dimensions = extents;
35✔
132
    tile_info_.bases = tile.min_subset;
35✔
133
    tile_info_.strides = std::vector<symbolic::Expression>(tile.layout.strides().begin(), tile.layout.strides().end());
35✔
134
    tile_info_.offset = tile.layout.offset();
35✔
135

136
    // GPU shared memory: resolve symbolic extents using GPU block sizes and
137
    // require at least one cooperative dimension
138
    if (storage_type_.is_nv_shared()) {
35✔
139
        auto ancestors = ControlFlowNode::parent_chain(loop_);
7✔
140

141
        // Build substitution map: symbolic GPU map bounds → integer block sizes
142
        // E.g., Map condition "i < N" with block_size=32 → N=32
143
        for (auto* node : ancestors) {
37✔
144
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
37✔
145
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
13✔
146
                    continue;
×
147
                }
×
148
                auto block_size = gpu::gpu_block_size(ancestor_map->schedule_type());
13✔
149
                // Extract symbolic bound from condition: Lt(indvar, BOUND)
150
                auto condition = ancestor_map->condition();
13✔
151
                if (SymEngine::is_a<SymEngine::StrictLessThan>(*condition)) {
13✔
152
                    auto stl = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(condition);
13✔
153
                    auto rhs = stl->get_args()[1];
13✔
154
                    auto iter_count = symbolic::sub(rhs, ancestor_map->init());
13✔
155
                    if (!SymEngine::is_a<SymEngine::Integer>(*iter_count)) {
13✔
156
                        // Symbolic bound — substitute with block size in extents and bases
157
                        for (auto& ext : tile_info_.dimensions) {
16✔
158
                            ext = symbolic::simplify(symbolic::subs(ext, iter_count, block_size));
16✔
159
                        }
16✔
160
                        for (auto& base : tile_info_.bases) {
16✔
161
                            base = symbolic::simplify(symbolic::subs(base, iter_count, block_size));
16✔
162
                        }
16✔
163
                    }
9✔
164
                }
13✔
165
            }
13✔
166
        }
37✔
167

168
        // Also resolve the loop's own bound if symbolic and matches a block size
169
        // E.g., For k = 0..K where K is a parameter — check if K can be resolved
170
        // from any GPU ancestor map
171
        // (Already handled above: if K appears as a GPU map bound, it's substituted)
172

173
        // Criterion: All extents must now be provably integer
174
        for (auto& ext : tile_info_.dimensions) {
13✔
175
            if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
13✔
176
                return false;
2✔
177
            }
2✔
178
        }
13✔
179

180
        // Criterion: At least one cooperative dimension
181
        bool has_cooperative_dim = false;
5✔
182
        for (auto* node : ancestors) {
16✔
183
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
16✔
184
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
6✔
185
                    continue;
×
186
                }
×
187
                // A GPU dim is cooperative if its indvar does NOT appear in any tile base
188
                bool appears_in_bases = false;
6✔
189
                for (auto& base : tile_info_.bases) {
10✔
190
                    if (symbolic::uses(base, ancestor_map->indvar())) {
10✔
191
                        appears_in_bases = true;
1✔
192
                        break;
1✔
193
                    }
1✔
194
                }
10✔
195
                if (!appears_in_bases) {
6✔
196
                    has_cooperative_dim = true;
5✔
197
                    break;
5✔
198
                }
5✔
199
            }
6✔
200
        }
16✔
201
        if (!has_cooperative_dim) {
5✔
202
            return false;
×
203
        }
×
204
    } else {
28✔
205
        // CPU_Stack must not be applied at or above the GPU kernel boundary.
206
        // Check whether the loop is outside any GPU region by looking for
207
        // GPU-scheduled ancestors.
208
        auto ancestors = ControlFlowNode::parent_chain(loop_);
28✔
209
        bool has_gpu_ancestor = false;
28✔
210
        for (auto* node : ancestors) {
110✔
211
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
110✔
NEW
212
                if (gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
×
NEW
213
                    has_gpu_ancestor = true;
×
NEW
214
                    break;
×
UNCOV
215
                }
×
NEW
216
            }
×
217
        }
110✔
218

219
        if (!has_gpu_ancestor) {
28✔
220
            // The loop is outside any GPU kernel.  Reject if the loop itself
221
            // is GPU-scheduled (it IS the kernel boundary) or if its body
222
            // contains GPU-scheduled maps (buffer on host, referenced in kernel).
223
            if (auto* self_map = dynamic_cast<structured_control_flow::Map*>(&loop_)) {
28✔
224
                if (gpu::is_gpu_schedule(self_map->schedule_type())) {
3✔
225
                    return false;
2✔
226
                }
2✔
227
            }
3✔
228

229
            auto& loop_analysis = analysis_manager.get<analysis::LoopAnalysis>();
26✔
230
            for (auto* desc : loop_analysis.descendants(&loop_)) {
26✔
231
                if (auto* desc_map = dynamic_cast<structured_control_flow::Map*>(desc)) {
25✔
232
                    if (gpu::is_gpu_schedule(desc_map->schedule_type())) {
1✔
233
                        return false;
1✔
234
                    }
1✔
235
                }
1✔
236
            }
25✔
237
        }
26✔
238

239
        // CPU_Stack inside a GPU region is only valid for per-thread locals
240
        // (all GPU map indvars appear in the tile bases). If there is a
241
        // cooperative dimension (a GPU indvar NOT in the bases), the buffer
242
        // must be NV_Shared so all threads in the block can see each other's reads.
243
        for (auto* node : ancestors) {
105✔
244
            if (auto* ancestor_map = dynamic_cast<structured_control_flow::Map*>(node)) {
105✔
245
                if (!gpu::is_gpu_schedule(ancestor_map->schedule_type())) {
×
246
                    continue;
×
247
                }
×
248
                bool appears_in_bases = false;
×
249
                for (auto& base : tile_info_.bases) {
×
250
                    if (symbolic::uses(base, ancestor_map->indvar())) {
×
251
                        appears_in_bases = true;
×
252
                        break;
×
253
                    }
×
254
                }
×
255
                if (!appears_in_bases) {
×
256
                    // Cooperative dimension detected with CPU_Stack — invalid.
257
                    // The buffer requires NV_Shared for cross-thread visibility.
258
                    return false;
×
259
                }
×
260
            }
×
261
        }
105✔
262
    }
25✔
263

264
    return true;
30✔
265
}
35✔
266

267
void InLocalStorage::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
23✔
268
    auto& sdfg = builder.subject();
23✔
269

270
    auto parent_node = loop_.get_parent();
23✔
271
    auto parent = dynamic_cast<structured_control_flow::Sequence*>(parent_node);
23✔
272
    if (!parent) {
23✔
273
        throw InvalidSDFGException("InLocalStorage: Parent of loop must be a Sequence!");
×
274
    }
×
275

276
    // We replace all relevant memlets with flat local indices
277
    // Thus, we now use a flat pointer to index into container
278
    // Remark: sdfg.type may return an opaque pointer, so use
279
    //         memlet instead
280
    auto* memlet = *group_memlets_.begin();
23✔
281
    types::Scalar scalar_type(memlet->base_type().primitive_type());
23✔
282
    types::Pointer pointer_type(scalar_type);
23✔
283

284
    // Create local buffer name
285
    local_name_ = builder.find_new_name("__daisy_in_local_storage_" + this->container_);
23✔
286

287
    // Collect varying dimensions (extent > 1) and their sizes
288
    std::vector<size_t> varying_dims;
23✔
289
    std::vector<symbolic::Expression> varying_dim_sizes;
23✔
290
    for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
64✔
291
        auto& dim_size = tile_info_.dimensions.at(d);
41✔
292
        if (!symbolic::eq(dim_size, symbolic::integer(1))) {
41✔
293
            varying_dims.push_back(d);
32✔
294
            varying_dim_sizes.push_back(dim_size);
32✔
295
        }
32✔
296
    }
41✔
297

298
    // GPU classification: each ancestor GPU Map is either
299
    //  - per-thread (its Map indvar appears in tile.bases — each thread sees a
300
    //    distinct slice along that dim, so the shared buffer gets its own
301
    //    per-thread slot indexed by the within-block thread_idx), or
302
    //  - cooperative (Map indvar not in bases — all threads along that dim
303
    //    cooperatively load the same shared tile, strided by thread_idx).
304
    struct GpuDim {
23✔
305
        gpu::GPUDimension dim;
23✔
306
        symbolic::Symbol map_indvar; // global thread index (== thread_idx + blockIdx * blockDim)
23✔
307
        symbolic::Symbol thread_idx; // within-block thread index (NV_Symbol)
23✔
308
        symbolic::Integer block_size;
23✔
309
        bool is_per_thread;
23✔
310
    };
23✔
311
    std::vector<GpuDim> per_thread_dims; // populated only on GPU path
23✔
312
    std::vector<GpuDim> coop_dims; // populated only on GPU path
23✔
313
    bool is_rocm = false;
23✔
314

315
    if (storage_type_.is_nv_shared()) {
23✔
316
        auto ancestors = ControlFlowNode::parent_chain(loop_);
5✔
317
        for (auto* node : ancestors) {
29✔
318
            auto* m = dynamic_cast<structured_control_flow::Map*>(node);
29✔
319
            if (!m || !gpu::is_gpu_schedule(m->schedule_type())) continue;
29✔
320
            if (m->schedule_type().value() == "ROCM") {
10✔
321
                is_rocm = true;
×
322
                break;
×
323
            }
×
324
        }
10✔
325
        const std::string prefix = is_rocm ? "__daisy_hip_thread_idx_" : "__daisy_cuda_thread_idx_";
5✔
326
        auto suffix = [](gpu::GPUDimension d) -> std::string {
10✔
327
            switch (d) {
10✔
328
                case gpu::GPUDimension::X:
5✔
329
                    return "x";
5✔
330
                case gpu::GPUDimension::Y:
5✔
331
                    return "y";
5✔
332
                case gpu::GPUDimension::Z:
×
333
                    return "z";
×
334
            }
10✔
335
            return "?";
×
336
        };
10✔
337
        for (auto* node : ancestors) {
29✔
338
            auto* m = dynamic_cast<structured_control_flow::Map*>(node);
29✔
339
            if (!m || !gpu::is_gpu_schedule(m->schedule_type())) continue;
29✔
340
            GpuDim gd;
10✔
341
            gd.dim = gpu::gpu_dimension(m->schedule_type());
10✔
342
            gd.map_indvar = m->indvar();
10✔
343
            gd.thread_idx = symbolic::symbol(prefix + suffix(gd.dim));
10✔
344
            gd.block_size = gpu::gpu_block_size(m->schedule_type());
10✔
345
            gd.is_per_thread = false;
10✔
346
            for (auto& base : tile_info_.bases) {
15✔
347
                if (symbolic::uses(base, m->indvar())) {
15✔
348
                    gd.is_per_thread = true;
4✔
349
                    break;
4✔
350
                }
4✔
351
            }
15✔
352
            (gd.is_per_thread ? per_thread_dims : coop_dims).push_back(gd);
10✔
353
        }
10✔
354
        auto by_dim = [](const GpuDim& a, const GpuDim& b) {
5✔
355
            return static_cast<int>(a.dim) < static_cast<int>(b.dim);
1✔
356
        };
1✔
357
        std::sort(per_thread_dims.begin(), per_thread_dims.end(), by_dim);
5✔
358
        std::sort(coop_dims.begin(), coop_dims.end(), by_dim);
5✔
359

360
        // Ensure within-block thread_idx containers exist. Codegen recognises
361
        // NV_Symbol-typed scalars and substitutes them with threadIdx.{x,y,z}
362
        // (CUDA) or the ROCm equivalent at emission time.
363
        auto ensure_idx = [&](const symbolic::Symbol& sym) {
10✔
364
            if (!sdfg.exists(sym->get_name())) {
10✔
365
                types::Scalar idx_type(types::PrimitiveType::Int32);
8✔
366
                idx_type.storage_type(types::StorageType::NV_Symbol());
8✔
367
                builder.add_container(sym->get_name(), idx_type);
8✔
368
            }
8✔
369
        };
10✔
370
        for (auto& gd : per_thread_dims) ensure_idx(gd.thread_idx);
5✔
371
        for (auto& gd : coop_dims) ensure_idx(gd.thread_idx);
6✔
372
    }
5✔
373

374
    // Buffer dim sizes: [per-thread block sizes (X, Y, Z canonical order)] ++
375
    //                   [varying tile dim sizes (original access-dim order)]
376
    std::vector<symbolic::Expression> buf_dim_sizes;
23✔
377
    for (auto& gd : per_thread_dims) buf_dim_sizes.push_back(gd.block_size);
23✔
378
    for (auto& s : varying_dim_sizes) buf_dim_sizes.push_back(s);
32✔
379

380
    // Total buffer size (number of scalar slots)
381
    symbolic::Expression total_size = symbolic::integer(1);
23✔
382
    for (auto& s : buf_dim_sizes) total_size = symbolic::mul(total_size, s);
36✔
383

384
    // Per-thread index prefix (each thread's fixed buffer coords)
385
    std::vector<symbolic::Expression> per_thread_indices;
23✔
386
    for (auto& gd : per_thread_dims) per_thread_indices.push_back(gd.thread_idx);
23✔
387

388
    // Row-major linearization over buf_dim_sizes (leftmost dim = outermost stride)
389
    auto linearize_exprs = [&](const std::vector<symbolic::Expression>& indices) -> symbolic::Expression {
50✔
390
        symbolic::Expression linear_idx = symbolic::integer(0);
50✔
391
        symbolic::Expression stride = symbolic::integer(1);
50✔
392
        for (int i = static_cast<int>(indices.size()) - 1; i >= 0; i--) {
130✔
393
            linear_idx = symbolic::add(linear_idx, symbolic::mul(indices[i], stride));
80✔
394
            stride = symbolic::mul(stride, buf_dim_sizes[i]);
80✔
395
        }
80✔
396
        return linear_idx;
50✔
397
    };
50✔
398

399
    // Helper: build linearized local index from per-dimension indvars (symbols)
400
    auto linearize = [&](const std::vector<symbolic::Symbol>& indvars) -> symbolic::Expression {
23✔
401
        std::vector<symbolic::Expression> exprs(indvars.begin(), indvars.end());
18✔
402
        return linearize_exprs(exprs);
18✔
403
    };
18✔
404

405
    // Helper: build source subset (base[d] + copy_indvar[d]) for original container
406
    auto build_original_subset = [&](const std::vector<symbolic::Expression>& copy_indices) -> data_flow::Subset {
23✔
407
        std::vector<symbolic::Expression> full_indices;
23✔
408
        size_t var_idx = 0;
23✔
409
        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
64✔
410
            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
41✔
411
                full_indices.push_back(symbolic::add(tile_info_.bases.at(d), copy_indices.at(var_idx++)));
32✔
412
            } else {
32✔
413
                full_indices.push_back(tile_info_.bases.at(d));
9✔
414
            }
9✔
415
        }
41✔
416

417
        symbolic::Expression linear = tile_info_.offset;
23✔
418
        for (size_t d = 0; d < full_indices.size(); d++) {
64✔
419
            linear = symbolic::add(linear, symbolic::mul(tile_info_.strides.at(d), full_indices.at(d)));
41✔
420
        }
41✔
421
        return {linear};
23✔
422
    };
23✔
423

424
    // ==================================================================
425
    // Branch: GPU cooperative path vs CPU sequential path
426
    // ==================================================================
427
    if (storage_type_.is_nv_shared()) {
23✔
428
        // ============================================================
429
        // GPU COOPERATIVE PATH
430
        // ============================================================
431
        // Each thread owns a fixed slot along per-thread buffer dims and
432
        // strides through the varying-flat range with the other threads
433
        // sharing that slot (i.e. threads in cooperative dims only).
434

435
        // Total cooperative-thread count (= 1 if no cooperative dims)
436
        symbolic::Expression total_coop_threads = symbolic::integer(1);
5✔
437
        for (auto& cd : coop_dims) {
6✔
438
            total_coop_threads = symbolic::mul(total_coop_threads, cd.block_size);
6✔
439
        }
6✔
440

441
        // Flat within-block index over cooperative dims only (= 0 if none).
442
        // Row-major: X is least-significant when present.
443
        symbolic::Expression coop_flat = symbolic::integer(0);
5✔
444
        {
5✔
445
            symbolic::Expression stride = symbolic::integer(1);
5✔
446
            for (auto it = coop_dims.rbegin(); it != coop_dims.rend(); ++it) {
11✔
447
                coop_flat = symbolic::add(coop_flat, symbolic::mul(it->thread_idx, stride));
6✔
448
                stride = symbolic::mul(stride, it->block_size);
6✔
449
            }
6✔
450
        }
5✔
451

452
        // Varying-flat size = product of tile dim extents (excluding extent==1).
453
        // This is the address range each thread cooperatively walks within its
454
        // per-thread slot.
455
        symbolic::Expression varying_flat_size = symbolic::integer(1);
5✔
456
        for (auto& s : varying_dim_sizes) {
7✔
457
            varying_flat_size = symbolic::mul(varying_flat_size, s);
7✔
458
        }
7✔
459

460
        // Create the local buffer with NV_Shared storage
461
        types::Array buffer_type(storage_type_, 0, {}, scalar_type, total_size);
5✔
462
        builder.add_container(local_name_, buffer_type);
5✔
463

464
        // Emit: barrier → cooperative copy loop → barrier → main loop
465
        // 1. Barrier before copy
466
        auto& barrier_block1 = builder.add_block_before(*parent, loop_, {}, loop_.debug_info());
5✔
467
        builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block1, {});
5✔
468

469
        // 2. Cooperative copy: for (idx = coop_flat; idx < varying_flat_size; idx += total_coop_threads)
470
        auto idx_name = builder.find_new_name("__daisy_ils_coop_" + this->container_);
5✔
471
        types::Scalar idx_type(types::PrimitiveType::UInt64);
5✔
472
        builder.add_container(idx_name, idx_type);
5✔
473
        auto idx_var = symbolic::symbol(idx_name);
5✔
474

475
        auto& copy_loop = builder.add_map_before(
5✔
476
            *parent,
5✔
477
            loop_,
5✔
478
            idx_var,
5✔
479
            symbolic::Lt(idx_var, varying_flat_size),
5✔
480
            coop_flat,
5✔
481
            symbolic::add(idx_var, total_coop_threads),
5✔
482
            structured_control_flow::ScheduleType_Sequential::create(),
5✔
483
            {},
5✔
484
            loop_.debug_info()
5✔
485
        );
5✔
486

487
        auto& copy_scope = copy_loop.root();
5✔
488
        auto& copy_block = builder.add_block(copy_scope);
5✔
489
        auto& copy_src = builder.add_access(copy_block, this->container_);
5✔
490
        auto& copy_dst = builder.add_access(copy_block, local_name_);
5✔
491
        auto& copy_tasklet = builder.add_tasklet(copy_block, data_flow::TaskletCode::assign, "_out", {"_in"});
5✔
492

493
        // Decompose idx_var into per-varying-dim indices (row-major).
494
        // For a single varying dim this is just idx_var.
495
        std::vector<symbolic::Expression> varying_decomp;
5✔
496
        symbolic::Expression remainder = idx_var;
5✔
497
        for (size_t i = 0; i < varying_dim_sizes.size(); i++) {
12✔
498
            if (i + 1 < varying_dim_sizes.size()) {
7✔
499
                symbolic::Expression divisor = symbolic::integer(1);
2✔
500
                for (size_t j = i + 1; j < varying_dim_sizes.size(); j++) {
4✔
501
                    divisor = symbolic::mul(divisor, varying_dim_sizes[j]);
2✔
502
                }
2✔
503
                varying_decomp.push_back(symbolic::div(remainder, divisor));
2✔
504
                remainder = symbolic::mod(remainder, divisor);
2✔
505
            } else {
5✔
506
                varying_decomp.push_back(remainder);
5✔
507
            }
5✔
508
        }
7✔
509

510
        // Source = original container at (bases — which already use the global
511
        // Map indvars — plus the varying decomposition along each varying dim).
512
        auto copy_src_subset = build_original_subset(varying_decomp);
5✔
513

514
        // Destination = buffer at (per_thread_indices ++ varying_decomp) linearized.
515
        std::vector<symbolic::Expression> dest_indices = per_thread_indices;
5✔
516
        for (auto& v : varying_decomp) dest_indices.push_back(v);
7✔
517
        data_flow::Subset copy_dst_subset = {linearize_exprs(dest_indices)};
5✔
518

519
        builder.add_computational_memlet(copy_block, copy_src, copy_tasklet, "_in", copy_src_subset, pointer_type);
5✔
520
        builder.add_computational_memlet(copy_block, copy_tasklet, "_out", copy_dst, copy_dst_subset, buffer_type);
5✔
521

522
        // 3. Barrier after copy
523
        auto& barrier_block2 = builder.add_block_before(*parent, loop_, {}, loop_.debug_info());
5✔
524
        builder.add_library_node<data_flow::BarrierLocalNode>(barrier_block2, {});
5✔
525
    } else {
18✔
526
        // ============================================================
527
        // CPU SEQUENTIAL PATH
528
        // ============================================================
529
        // Create the local buffer with specified storage type
530
        types::Array buffer_type(storage_type_, 0, {}, scalar_type, total_size);
18✔
531
        builder.add_container(local_name_, buffer_type);
18✔
532

533
        std::vector<symbolic::Symbol> copy_indvars;
18✔
534
        structured_control_flow::Sequence* copy_scope =
18✔
535
            &builder.add_sequence_before(*parent, loop_, {}, loop_.debug_info());
18✔
536
        for (size_t i = 0; i < varying_dims.size(); i++) {
43✔
537
            size_t d = varying_dims[i];
25✔
538
            auto indvar_name = builder.find_new_name("__daisy_ils_" + this->container_ + "_d" + std::to_string(d));
25✔
539
            types::Scalar indvar_type(types::PrimitiveType::UInt64);
25✔
540
            builder.add_container(indvar_name, indvar_type);
25✔
541
            auto indvar = symbolic::symbol(indvar_name);
25✔
542
            copy_indvars.push_back(indvar);
25✔
543

544
            auto init = symbolic::integer(0);
25✔
545
            auto condition = symbolic::Lt(indvar, varying_dim_sizes[i]);
25✔
546
            auto update = symbolic::add(indvar, symbolic::integer(1));
25✔
547

548
            auto& copy_loop = builder.add_map(
25✔
549
                *copy_scope,
25✔
550
                indvar,
25✔
551
                condition,
25✔
552
                init,
25✔
553
                update,
25✔
554
                structured_control_flow::ScheduleType_Sequential::create(),
25✔
555
                {},
25✔
556
                loop_.debug_info()
25✔
557
            );
25✔
558
            copy_scope = &copy_loop.root();
25✔
559
        }
25✔
560

561
        // Create copy block
562
        auto& copy_block = builder.add_block(*copy_scope);
18✔
563
        auto& copy_src = builder.add_access(copy_block, this->container_);
18✔
564
        auto& copy_dst = builder.add_access(copy_block, local_name_);
18✔
565
        auto& copy_tasklet = builder.add_tasklet(copy_block, data_flow::TaskletCode::assign, "_out", {"_in"});
18✔
566

567
        std::vector<symbolic::Expression> copy_exprs(copy_indvars.begin(), copy_indvars.end());
18✔
568
        auto copy_src_subset = build_original_subset(copy_exprs);
18✔
569
        data_flow::Subset copy_dst_subset = {linearize(copy_indvars)};
18✔
570

571
        builder.add_computational_memlet(copy_block, copy_src, copy_tasklet, "_in", copy_src_subset, pointer_type);
18✔
572
        types::Array buffer_type_ref(storage_type_, 0, {}, scalar_type, total_size);
18✔
573
        builder.add_computational_memlet(copy_block, copy_tasklet, "_out", copy_dst, copy_dst_subset, buffer_type_ref);
18✔
574
    }
18✔
575

576
    // ==================================================================
577
    // Update accesses in the main loop to use the local buffer
578
    // ==================================================================
579
    types::Array buffer_type(storage_type_, 0, {}, scalar_type, total_size);
23✔
580
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
23✔
581

582
    // Recursive helper to traverse all blocks in the loop body
583
    std::function<void(structured_control_flow::ControlFlowNode&)> rewrite_accesses;
23✔
584
    rewrite_accesses = [&](structured_control_flow::ControlFlowNode& node) {
93✔
585
        if (auto* block = dynamic_cast<structured_control_flow::Block*>(&node)) {
93✔
586
            auto& dfg = block->dataflow();
34✔
587

588
            // Collect access nodes to process (avoid iterator invalidation)
589
            std::vector<data_flow::AccessNode*> access_nodes;
34✔
590
            for (auto* access_node : dfg.data_nodes()) {
99✔
591
                if (access_node->data() == this->container_) {
99✔
592
                    access_nodes.push_back(access_node);
28✔
593
                }
28✔
594
            }
99✔
595

596
            for (auto* access : access_nodes) {
34✔
597
                // Classify memlets: group vs non-group
598
                struct MemletRewrite {
28✔
599
                    data_flow::Memlet* memlet;
28✔
600
                    data_flow::Subset local_subset;
28✔
601
                    bool is_outgoing;
28✔
602
                };
28✔
603
                std::vector<MemletRewrite> group_rewrites;
28✔
604
                bool all_in_group = true;
28✔
605

606
                for (auto& memlet : dfg.out_edges(*access)) {
29✔
607
                    if (group_memlets_.count(&memlet) == 0) {
29✔
608
                        all_in_group = false;
2✔
609
                        continue;
2✔
610
                    }
2✔
611
                    auto* acc = mla.access(memlet);
27✔
612
                    if (acc && acc->subset.size() == tile_info_.dimensions.size()) {
27✔
613
                        // Buffer index: [per-thread thread_idx (X,Y,Z order)] ++ [varying d: subset[d] - base[d]]
614
                        std::vector<symbolic::Expression> local_indices = per_thread_indices;
27✔
615
                        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
76✔
616
                            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
49✔
617
                                local_indices.push_back(symbolic::sub(acc->subset.at(d), tile_info_.bases.at(d)));
40✔
618
                            }
40✔
619
                        }
49✔
620
                        symbolic::Expression linear_idx = linearize_exprs(local_indices);
27✔
621
                        group_rewrites.push_back({&memlet, {linear_idx}, true});
27✔
622
                    }
27✔
623
                }
27✔
624
                for (auto& memlet : dfg.in_edges(*access)) {
28✔
625
                    if (group_memlets_.count(&memlet) == 0) {
×
626
                        all_in_group = false;
×
627
                        continue;
×
628
                    }
×
629
                    auto* acc = mla.access(memlet);
×
630
                    if (acc && acc->subset.size() == tile_info_.dimensions.size()) {
×
631
                        // Buffer index: [per-thread thread_idx (X,Y,Z order)] ++ [varying d: subset[d] - base[d]]
632
                        std::vector<symbolic::Expression> local_indices = per_thread_indices;
×
633
                        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
×
634
                            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
×
635
                                local_indices.push_back(symbolic::sub(acc->subset.at(d), tile_info_.bases.at(d)));
×
636
                            }
×
637
                        }
×
638
                        symbolic::Expression linear_idx = linearize_exprs(local_indices);
×
639
                        group_rewrites.push_back({&memlet, {linear_idx}, false});
×
640
                    }
×
641
                }
×
642

643
                if (group_rewrites.empty()) continue;
28✔
644

645
                if (all_in_group) {
27✔
646
                    // Simple case: all memlets in group → rewrite in-place and rename
647
                    for (auto& rw : group_rewrites) {
26✔
648
                        rw.memlet->set_subset(rw.local_subset);
26✔
649
                        rw.memlet->set_base_type(buffer_type);
26✔
650
                    }
26✔
651
                    access->data(local_name_);
26✔
652
                } else {
26✔
653
                    // Mixed case: split — create new local access node, redirect group memlets
654
                    auto& local_access = builder.add_access(*block, local_name_);
1✔
655
                    for (auto& rw : group_rewrites) {
1✔
656
                        if (rw.is_outgoing) {
1✔
657
                            // outgoing: access→tasklet  →  local_access→tasklet
658
                            auto& dst_node = rw.memlet->dst();
1✔
659
                            auto dst_conn = rw.memlet->dst_conn();
1✔
660
                            builder.remove_memlet(*block, *rw.memlet);
1✔
661
                            builder.add_memlet(
1✔
662
                                *block, local_access, "void", dst_node, dst_conn, rw.local_subset, buffer_type, {}
1✔
663
                            );
1✔
664
                        } else {
1✔
665
                            // incoming: tasklet→access  →  tasklet→local_access
666
                            auto& src_node = rw.memlet->src();
×
667
                            auto src_conn = rw.memlet->src_conn();
×
668
                            builder.remove_memlet(*block, *rw.memlet);
×
669
                            builder.add_memlet(
×
670
                                *block, src_node, src_conn, local_access, "void", rw.local_subset, buffer_type, {}
×
671
                            );
×
672
                        }
×
673
                    }
1✔
674
                }
1✔
675
            }
27✔
676
        } else if (auto* seq = dynamic_cast<structured_control_flow::Sequence*>(&node)) {
59✔
677
            for (size_t i = 0; i < seq->size(); i++) {
93✔
678
                rewrite_accesses(seq->at(i).first);
52✔
679
            }
52✔
680
        } else if (auto* loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&node)) {
41✔
681
            rewrite_accesses(loop->root());
18✔
682
        } else if (auto* if_else = dynamic_cast<structured_control_flow::IfElse*>(&node)) {
18✔
683
            for (size_t i = 0; i < if_else->size(); i++) {
×
684
                rewrite_accesses(if_else->at(i).first);
×
685
            }
×
686
        }
×
687
    };
93✔
688
    rewrite_accesses(loop_.root());
23✔
689

690
    // Cleanup
691
    analysis_manager.invalidate_all();
23✔
692

693
    passes::SequenceFusion sf_pass;
23✔
694
    passes::DeadCFGElimination dce_pass;
23✔
695
    bool applies = false;
23✔
696
    do {
41✔
697
        applies = false;
41✔
698
        applies |= dce_pass.run(builder, analysis_manager);
41✔
699
        applies |= sf_pass.run(builder, analysis_manager);
41✔
700
    } while (applies);
41✔
701
}
23✔
702

703
void InLocalStorage::to_json(nlohmann::json& j) const {
8✔
704
    j["transformation_type"] = this->name();
8✔
705
    j["parameters"] = nlohmann::json::object();
8✔
706

707
    serializer::JSONSerializer serializer_full;
8✔
708
    j["parameters"]["storage_type"] = nlohmann::json::object();
8✔
709
    serializer_full.storage_type_to_json(j["parameters"]["storage_type"], storage_type_);
8✔
710

711
    serializer::JSONSerializer ser_flat(false);
8✔
712
    j["subgraph"] = nlohmann::json::object();
8✔
713
    j["subgraph"]["0"] = nlohmann::json::object();
8✔
714
    ser_flat.serialize_node(j["subgraph"]["0"], loop_);
8✔
715

716
    j["subgraph"]["1"] = nlohmann::json::object();
8✔
717
    j["subgraph"]["1"]["element_id"] = access_node_.element_id();
8✔
718
    j["subgraph"]["1"]["type"] = "access_node";
8✔
719
}
8✔
720

721
InLocalStorage InLocalStorage::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
3✔
722
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
3✔
723
    auto element = builder.find_element_by_id(loop_id);
3✔
724
    if (!element) {
3✔
725
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
726
    }
×
727
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
3✔
728
    if (!loop) {
3✔
729
        throw InvalidTransformationDescriptionException(
×
730
            "Element with ID " + std::to_string(loop_id) + " is not a structured loop."
×
731
        );
×
732
    }
×
733

734
    auto access_node = dynamic_cast<
3✔
735
        data_flow::AccessNode*>(builder.find_element_by_id(desc.at("subgraph").at("1").at("element_id").get<size_t>()));
3✔
736
    if (!access_node) {
3✔
737
        throw InvalidTransformationDescriptionException(
×
738
            "Access node with ID " + std::to_string(desc.at("subgraph").at("1").at("element_id").get<size_t>()) +
×
739
            " not found."
×
740
        );
×
741
    }
×
742

743
    types::StorageType storage_type = types::StorageType::CPU_Stack();
3✔
744
    if (desc["parameters"].contains("storage_type")) {
3✔
745
        serializer::JSONSerializer serializer_full;
3✔
746
        storage_type = serializer_full.json_to_storage_type(desc["parameters"]["storage_type"]);
3✔
747
    }
3✔
748

749
    return InLocalStorage(*loop, *access_node, storage_type);
3✔
750
}
3✔
751

752
} // namespace transformations
753
} // 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