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

daisytuner / docc / 25113223833

29 Apr 2026 01:57PM UTC coverage: 64.192% (-0.08%) from 64.274%
25113223833

Pull #693

github

web-flow
Merge 17019ae01 into 514fbe883
Pull Request #693: unifies local storage api

269 of 350 new or added lines in 4 files covered. (76.86%)

27 existing lines in 6 files now uncovered.

30850 of 48059 relevant lines covered (64.19%)

593.67 hits per line

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

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

3
#include <cstddef>
4
#include <string>
5

6
#include "sdfg/analysis/memory_layout_analysis.h"
7
#include "sdfg/analysis/scope_analysis.h"
8
#include "sdfg/analysis/users.h"
9
#include "sdfg/builder/structured_sdfg_builder.h"
10
#include "sdfg/data_flow/access_node.h"
11
#include "sdfg/data_flow/memlet.h"
12
#include "sdfg/passes/structured_control_flow/dead_cfg_elimination.h"
13
#include "sdfg/passes/structured_control_flow/sequence_fusion.h"
14
#include "sdfg/structured_control_flow/sequence.h"
15
#include "sdfg/structured_control_flow/structured_loop.h"
16
#include "sdfg/symbolic/symbolic.h"
17
#include "sdfg/types/array.h"
18
#include "sdfg/types/pointer.h"
19
#include "sdfg/types/scalar.h"
20

21
namespace sdfg {
22
namespace transformations {
23

24
OutLocalStorage::OutLocalStorage(structured_control_flow::StructuredLoop& loop, const data_flow::AccessNode& access_node)
25
    : loop_(loop), access_node_(access_node), container_(access_node.data()) {};
16✔
26

27
std::string OutLocalStorage::name() const { return "OutLocalStorage"; };
4✔
28

29
bool OutLocalStorage::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
14✔
30
    auto& sdfg = builder.subject();
14✔
31
    auto& body = this->loop_.root();
14✔
32

33
    tile_info_ = TileInfo{};
14✔
34

35
    // Criterion: Container must exist
36
    if (!sdfg.exists(this->container_)) {
14✔
NEW
37
        return false;
×
NEW
38
    }
×
39

40
    auto& type = sdfg.type(this->container_);
14✔
41

42
    // Criterion: Container must be used in the loop body
43
    auto& users = analysis_manager.get<analysis::Users>();
14✔
44
    analysis::UsersView body_users(users, body);
14✔
45
    if (body_users.uses(this->container_).empty()) {
14✔
46
        return false;
2✔
47
    }
2✔
48

49
    // Criterion: Container must have writes (this is OutLocalStorage, not InLocalStorage)
50
    if (body_users.writes(this->container_).empty()) {
12✔
51
        return false;
1✔
52
    }
1✔
53

54
    // Determine if container is also read (read-write vs write-only)
55
    tile_info_.has_read = !body_users.reads(this->container_).empty();
11✔
56

57
    // Handle scalar containers: no tile needed, dimensions stay empty
58
    if (type.type_id() == types::TypeID::Scalar) {
11✔
59
        return true;
1✔
60
    }
1✔
61

62
    // For Array/Pointer types: use MemoryLayoutAnalysis tile API
63
    if (type.type_id() != types::TypeID::Pointer && type.type_id() != types::TypeID::Array) {
10✔
NEW
64
        return false;
×
UNCOV
65
    }
×
66

67
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
10✔
68
    auto* tile = mla.tile(loop_, this->container_);
10✔
69
    if (!tile) {
10✔
70
        return false;
1✔
71
    }
1✔
72

73
    // Get overapproximated extents (integer upper bounds)
74
    auto extents = tile->extents_approx();
9✔
75
    if (extents.empty()) {
9✔
NEW
76
        return false;
×
UNCOV
77
    }
×
78

79
    // Criterion: All extents must be provably integer
80
    for (size_t idx = 0; idx < extents.size(); idx++) {
20✔
81
        auto& ext = extents[idx];
11✔
82
        if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
11✔
83
            return false;
×
84
        }
×
85
    }
11✔
86

87
    // Store tile info
88
    tile_info_.dimensions = extents;
9✔
89
    tile_info_.bases = tile->min_subset;
9✔
90
    tile_info_.strides =
9✔
91
        std::vector<symbolic::Expression>(tile->layout.strides().begin(), tile->layout.strides().end());
9✔
92
    tile_info_.offset = tile->layout.offset();
9✔
93

94
    return true;
9✔
95
}
9✔
96

97
void OutLocalStorage::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
9✔
98
    auto& sdfg = builder.subject();
9✔
99
    auto& users = analysis_manager.get<analysis::Users>();
9✔
100
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
9✔
101

102
    auto parent_node = scope_analysis.parent_scope(&loop_);
9✔
103
    auto parent = dynamic_cast<structured_control_flow::Sequence*>(parent_node);
9✔
104
    if (!parent) {
9✔
105
        throw InvalidSDFGException("OutLocalStorage: Parent of loop must be a Sequence!");
×
106
    }
×
107

108
    // Get type information
109
    auto& type = sdfg.type(this->container_);
9✔
110
    types::Scalar scalar_type(type.primitive_type());
9✔
111

112
    // Create local buffer name
113
    local_name_ = "__daisy_out_local_storage_" + this->container_;
9✔
114

115
    // ========================================================================
116
    // SCALAR PATH: tile_info_.dimensions is empty
117
    // ========================================================================
118
    if (tile_info_.dimensions.empty()) {
9✔
119
        // Create scalar local buffer
120
        builder.add_container(local_name_, scalar_type);
1✔
121

122
        // Get the access subset from the first user (all scalar, so empty subset)
123
        analysis::UsersView body_users(users, loop_.root());
1✔
124
        auto accesses = body_users.uses(this->container_);
1✔
125
        auto first_access = accesses.at(0);
1✔
126
        auto first_subset = first_access->subsets().at(0);
1✔
127

128
        // Init block (copy from container to local) - before loop
129
        if (tile_info_.has_read) {
1✔
130
            auto& init_block = builder.add_block_before(*parent, loop_, {}, loop_.debug_info());
1✔
131
            auto& init_src = builder.add_access(init_block, this->container_);
1✔
132
            auto& init_dst = builder.add_access(init_block, local_name_);
1✔
133
            auto& init_tasklet = builder.add_tasklet(init_block, data_flow::TaskletCode::assign, "_out", {"_in"});
1✔
134
            builder.add_computational_memlet(init_block, init_src, init_tasklet, "_in", first_subset, type);
1✔
135
            builder.add_computational_memlet(init_block, init_tasklet, "_out", init_dst, {}, scalar_type);
1✔
136
        }
1✔
137

138
        // Writeback block (copy from local to container) - after loop
139
        {
1✔
140
            auto& wb_block = builder.add_block_after(*parent, loop_, {}, loop_.debug_info());
1✔
141
            auto& wb_src = builder.add_access(wb_block, local_name_);
1✔
142
            auto& wb_dst = builder.add_access(wb_block, this->container_);
1✔
143
            auto& wb_tasklet = builder.add_tasklet(wb_block, data_flow::TaskletCode::assign, "_out", {"_in"});
1✔
144
            builder.add_computational_memlet(wb_block, wb_src, wb_tasklet, "_in", {}, scalar_type);
1✔
145
            builder.add_computational_memlet(wb_block, wb_tasklet, "_out", wb_dst, first_subset, type);
1✔
146
        }
1✔
147

148
        // Rewrite body accesses to use scalar local
149
        for (auto* user : body_users.uses(this->container_)) {
2✔
150
            auto element = user->element();
2✔
151
            if (auto access = dynamic_cast<data_flow::AccessNode*>(element)) {
2✔
152
                for (auto& iedge : access->get_parent().in_edges(*access)) {
2✔
153
                    auto memlet = &iedge;
1✔
154
                    memlet->set_subset({});
1✔
155
                    memlet->set_base_type(scalar_type);
1✔
156
                }
1✔
157
                for (auto& oedge : access->get_parent().out_edges(*access)) {
2✔
158
                    auto memlet = &oedge;
1✔
159
                    memlet->set_subset({});
1✔
160
                    memlet->set_base_type(scalar_type);
1✔
161
                }
1✔
162
            }
2✔
163
        }
2✔
164

165
        // Replace container name in the loop body
166
        loop_.replace(symbolic::symbol(this->container_), symbolic::symbol(local_name_));
1✔
167
    }
1✔
168
    // ========================================================================
169
    // ARRAY PATH: tile_info_.dimensions is non-empty
170
    // ========================================================================
171
    else {
8✔
172
        // Collect varying dimensions (extent > 1) and compute buffer layout
173
        std::vector<size_t> varying_dims;
8✔
174
        std::vector<symbolic::Expression> dim_sizes;
8✔
175
        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
18✔
176
            auto& dim_size = tile_info_.dimensions.at(d);
10✔
177
            if (!symbolic::eq(dim_size, symbolic::integer(1))) {
10✔
178
                varying_dims.push_back(d);
9✔
179
                dim_sizes.push_back(dim_size);
9✔
180
            }
9✔
181
        }
10✔
182

183
        // Compute total buffer size
184
        symbolic::Expression total_size = symbolic::integer(1);
8✔
185
        for (auto& ds : dim_sizes) {
9✔
186
            total_size = symbolic::mul(total_size, ds);
9✔
187
        }
9✔
188

189
        // Create the local buffer
190
        types::Array buffer_type(scalar_type, total_size);
8✔
191
        builder.add_container(local_name_, buffer_type);
8✔
192

193
        // Helper: build linearized local index from per-dimension indices
194
        auto linearize = [&](const std::vector<symbolic::Symbol>& indvars) -> symbolic::Expression {
14✔
195
            symbolic::Expression linear_idx = symbolic::integer(0);
14✔
196
            symbolic::Expression stride = symbolic::integer(1);
14✔
197
            for (int i = indvars.size() - 1; i >= 0; i--) {
30✔
198
                linear_idx = symbolic::add(linear_idx, symbolic::mul(indvars[i], stride));
16✔
199
                stride = symbolic::mul(stride, dim_sizes[i]);
16✔
200
            }
16✔
201
            return linear_idx;
14✔
202
        };
14✔
203

204
        // Helper: build source subset (base[d] + copy_indvar[d]) for original container
205
        // For Pointer types: re-linearize to a single expression using layout strides
206
        // For Array types: produce multi-dimensional subset
207
        bool is_pointer = (type.type_id() == types::TypeID::Pointer);
8✔
208
        auto build_original_subset = [&](const std::vector<symbolic::Symbol>& copy_indvars) -> data_flow::Subset {
14✔
209
            // Build per-dimension indices: base[d] + indvar[d] for varying, base[d] for constant
210
            std::vector<symbolic::Expression> full_indices;
14✔
211
            size_t var_idx = 0;
14✔
212
            for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
32✔
213
                if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
18✔
214
                    full_indices.push_back(symbolic::add(tile_info_.bases.at(d), copy_indvars.at(var_idx++)));
16✔
215
                } else {
16✔
216
                    full_indices.push_back(tile_info_.bases.at(d));
2✔
217
                }
2✔
218
            }
18✔
219

220
            if (is_pointer) {
14✔
221
                // Linearize: offset + sum(stride[d] * index[d])
222
                symbolic::Expression linear = tile_info_.offset;
14✔
223
                for (size_t d = 0; d < full_indices.size(); d++) {
32✔
224
                    linear = symbolic::add(linear, symbolic::mul(tile_info_.strides.at(d), full_indices.at(d)));
18✔
225
                }
18✔
226
                return {linear};
14✔
227
            } else {
14✔
228
                // Multi-dimensional subset for Array types
NEW
229
                return data_flow::Subset(full_indices.begin(), full_indices.end());
×
NEW
230
            }
×
231
        };
14✔
232

233
        // ==================================================================
234
        // Create INIT loops (copy from container to tile) - before target loop
235
        // Only if the container is also read (read-write pattern)
236
        // ==================================================================
237
        if (tile_info_.has_read) {
8✔
238
            std::vector<symbolic::Symbol> init_indvars;
6✔
239
            structured_control_flow::Sequence* init_scope = parent;
6✔
240
            bool first_init_loop = true;
6✔
241

242
            for (size_t i = 0; i < varying_dims.size(); i++) {
13✔
243
                size_t d = varying_dims[i];
7✔
244
                auto indvar_name = "__daisy_ols_init_" + this->container_ + "_d" + std::to_string(d);
7✔
245
                types::Scalar indvar_type(types::PrimitiveType::UInt64);
7✔
246
                builder.add_container(indvar_name, indvar_type);
7✔
247
                auto indvar = symbolic::symbol(indvar_name);
7✔
248
                init_indvars.push_back(indvar);
7✔
249

250
                auto init = symbolic::integer(0);
7✔
251
                auto condition = symbolic::Lt(indvar, dim_sizes[i]);
7✔
252
                auto update = symbolic::add(indvar, symbolic::integer(1));
7✔
253

254
                if (first_init_loop) {
7✔
255
                    auto& init_loop =
5✔
256
                        builder
5✔
257
                            .add_for_before(*init_scope, loop_, indvar, condition, init, update, {}, loop_.debug_info());
5✔
258
                    init_scope = &init_loop.root();
5✔
259
                    first_init_loop = false;
5✔
260
                } else {
5✔
261
                    auto& init_loop =
2✔
262
                        builder.add_for(*init_scope, indvar, condition, init, update, {}, loop_.debug_info());
2✔
263
                    init_scope = &init_loop.root();
2✔
264
                }
2✔
265
            }
7✔
266

267
            // Create init copy block
268
            auto& init_block = builder.add_block(*init_scope);
6✔
269
            auto& init_src = builder.add_access(init_block, this->container_);
6✔
270
            auto& init_dst = builder.add_access(init_block, local_name_);
6✔
271
            auto& init_tasklet = builder.add_tasklet(init_block, data_flow::TaskletCode::assign, "_out", {"_in"});
6✔
272

273
            auto init_src_subset = build_original_subset(init_indvars);
6✔
274
            data_flow::Subset init_dst_subset = {linearize(init_indvars)};
6✔
275

276
            builder.add_computational_memlet(init_block, init_src, init_tasklet, "_in", init_src_subset, type);
6✔
277
            builder.add_computational_memlet(init_block, init_tasklet, "_out", init_dst, init_dst_subset, buffer_type);
6✔
278
        }
6✔
279

280
        // ==================================================================
281
        // Create WRITEBACK loops (copy from tile to container) - after loop
282
        // ==================================================================
283
        {
8✔
284
            std::vector<symbolic::Symbol> wb_indvars;
8✔
285
            structured_control_flow::Sequence* wb_scope = parent;
8✔
286
            bool first_wb_loop = true;
8✔
287

288
            for (size_t i = 0; i < varying_dims.size(); i++) {
17✔
289
                size_t d = varying_dims[i];
9✔
290
                auto indvar_name = "__daisy_ols_wb_" + this->container_ + "_d" + std::to_string(d);
9✔
291
                types::Scalar indvar_type(types::PrimitiveType::UInt64);
9✔
292
                builder.add_container(indvar_name, indvar_type);
9✔
293
                auto indvar = symbolic::symbol(indvar_name);
9✔
294
                wb_indvars.push_back(indvar);
9✔
295

296
                auto init = symbolic::integer(0);
9✔
297
                auto condition = symbolic::Lt(indvar, dim_sizes[i]);
9✔
298
                auto update = symbolic::add(indvar, symbolic::integer(1));
9✔
299

300
                if (first_wb_loop) {
9✔
301
                    auto& wb_loop =
7✔
302
                        builder.add_for_after(*wb_scope, loop_, indvar, condition, init, update, {}, loop_.debug_info());
7✔
303
                    wb_scope = &wb_loop.root();
7✔
304
                    first_wb_loop = false;
7✔
305
                } else {
7✔
306
                    auto& wb_loop = builder.add_for(*wb_scope, indvar, condition, init, update, {}, loop_.debug_info());
2✔
307
                    wb_scope = &wb_loop.root();
2✔
308
                }
2✔
309
            }
9✔
310

311
            // Create writeback copy block
312
            auto& wb_block = builder.add_block(*wb_scope);
8✔
313
            auto& wb_src = builder.add_access(wb_block, local_name_);
8✔
314
            auto& wb_dst = builder.add_access(wb_block, this->container_);
8✔
315
            auto& wb_tasklet = builder.add_tasklet(wb_block, data_flow::TaskletCode::assign, "_out", {"_in"});
8✔
316

317
            data_flow::Subset wb_src_subset = {linearize(wb_indvars)};
8✔
318
            auto wb_dst_subset = build_original_subset(wb_indvars);
8✔
319

320
            builder.add_computational_memlet(wb_block, wb_src, wb_tasklet, "_in", wb_src_subset, buffer_type);
8✔
321
            builder.add_computational_memlet(wb_block, wb_tasklet, "_out", wb_dst, wb_dst_subset, type);
8✔
322
        }
8✔
323

324
        // ==================================================================
325
        // Update accesses in the main loop to use the local buffer
326
        // ==================================================================
327
        analysis::UsersView body_users(users, loop_.root());
8✔
328
        auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
8✔
329

330
        for (auto* user : body_users.uses(this->container_)) {
14✔
331
            auto element = user->element();
14✔
332
            if (auto memlet = dynamic_cast<data_flow::Memlet*>(element)) {
14✔
333
                // Use MemoryLayoutAnalysis to get the delinearized access
NEW
334
                auto* access = mla.access(*memlet);
×
NEW
335
                if (access && access->subset.size() == tile_info_.dimensions.size()) {
×
336
                    // Compute local index: linearize (access[d] - base[d]) for varying dims
NEW
337
                    std::vector<symbolic::Expression> local_indices;
×
NEW
338
                    for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
×
NEW
339
                        if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
×
NEW
340
                            local_indices.push_back(symbolic::sub(access->subset.at(d), tile_info_.bases.at(d)));
×
NEW
341
                        }
×
NEW
342
                    }
×
343

344
                    // Linearize
NEW
345
                    symbolic::Expression linear_idx = symbolic::integer(0);
×
NEW
346
                    symbolic::Expression stride = symbolic::integer(1);
×
NEW
347
                    for (int i = local_indices.size() - 1; i >= 0; i--) {
×
NEW
348
                        linear_idx = symbolic::add(linear_idx, symbolic::mul(local_indices[i], stride));
×
NEW
349
                        stride = symbolic::mul(stride, dim_sizes[i]);
×
NEW
350
                    }
×
351

NEW
352
                    memlet->set_subset({linear_idx});
×
NEW
353
                    memlet->set_base_type(buffer_type);
×
NEW
354
                } else {
×
355
                    // Fallback: subtract bases from raw subset
NEW
356
                    auto& old_subset = memlet->subset();
×
NEW
357
                    if (old_subset.size() == tile_info_.dimensions.size()) {
×
NEW
358
                        std::vector<symbolic::Expression> local_indices;
×
NEW
359
                        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
×
NEW
360
                            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
×
NEW
361
                                local_indices.push_back(symbolic::sub(old_subset.at(d), tile_info_.bases.at(d)));
×
NEW
362
                            }
×
NEW
363
                        }
×
364

NEW
365
                        symbolic::Expression linear_idx = symbolic::integer(0);
×
NEW
366
                        symbolic::Expression stride = symbolic::integer(1);
×
NEW
367
                        for (int i = local_indices.size() - 1; i >= 0; i--) {
×
NEW
368
                            linear_idx = symbolic::add(linear_idx, symbolic::mul(local_indices[i], stride));
×
NEW
369
                            stride = symbolic::mul(stride, dim_sizes[i]);
×
NEW
370
                        }
×
371

NEW
372
                        memlet->set_subset({linear_idx});
×
NEW
373
                        memlet->set_base_type(buffer_type);
×
NEW
374
                    }
×
NEW
375
                }
×
UNCOV
376
            }
×
377
        }
14✔
378

379
        // Replace container name in the loop body
380
        loop_.replace(symbolic::symbol(this->container_), symbolic::symbol(local_name_));
8✔
381
    }
8✔
382

383
    // Cleanup
384
    analysis_manager.invalidate_all();
9✔
385

386
    passes::SequenceFusion sf_pass;
9✔
387
    passes::DeadCFGElimination dce_pass;
9✔
388
    bool applies = false;
9✔
389
    do {
9✔
390
        applies = false;
9✔
391
        applies |= dce_pass.run(builder, analysis_manager);
9✔
392
        applies |= sf_pass.run(builder, analysis_manager);
9✔
393
    } while (applies);
9✔
394
};
9✔
395

396
void OutLocalStorage::to_json(nlohmann::json& j) const {
2✔
397
    std::string loop_type;
2✔
398
    if (dynamic_cast<structured_control_flow::For*>(&loop_)) {
2✔
399
        loop_type = "for";
1✔
400
    } else if (dynamic_cast<structured_control_flow::Map*>(&loop_)) {
1✔
401
        loop_type = "map";
1✔
402
    } else {
1✔
403
        throw std::runtime_error("Unsupported loop type for serialization of loop: " + loop_.indvar()->get_name());
×
404
    }
×
405
    j["subgraph"] = {
2✔
406
        {"0", {{"element_id", this->loop_.element_id()}, {"type", loop_type}}},
2✔
407
        {"1", {{"element_id", this->access_node_.element_id()}, {"type", "access_node"}}}
2✔
408
    };
2✔
409
    j["transformation_type"] = this->name();
2✔
410
};
2✔
411

412
OutLocalStorage OutLocalStorage::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
1✔
413
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
1✔
414
    auto element = builder.find_element_by_id(loop_id);
1✔
415
    if (!element) {
1✔
416
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
417
    }
×
418
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
1✔
419

420
    auto access_node = dynamic_cast<
1✔
421
        data_flow::AccessNode*>(builder.find_element_by_id(desc.at("subgraph").at("1").at("element_id").get<size_t>()));
1✔
422
    if (!access_node) {
1✔
423
        throw InvalidTransformationDescriptionException(
×
424
            "Access node with ID " + std::to_string(desc.at("subgraph").at("1").at("element_id").get<size_t>()) +
×
425
            " not found."
×
426
        );
×
427
    }
×
428

429
    return OutLocalStorage(*loop, *access_node);
1✔
430
};
1✔
431

432
} // namespace transformations
433
} // 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