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

daisytuner / docc / 25194337763

30 Apr 2026 11:25PM UTC coverage: 64.283% (+0.009%) from 64.274%
25194337763

push

github

web-flow
Merge pull request #693 from daisytuner/local-storage-generalization

unifies local storage api

277 of 358 new or added lines in 5 files covered. (77.37%)

16 existing lines in 6 files now uncovered.

30899 of 48067 relevant lines covered (64.28%)

684.61 hits per line

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

70.85
/opt/src/transformations/in_local_storage.cpp
1
#include "sdfg/transformations/in_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
InLocalStorage::InLocalStorage(structured_control_flow::StructuredLoop& loop, const data_flow::AccessNode& access_node)
25
    : loop_(loop), access_node_(access_node), container_(access_node.data()) {}
22✔
26

27
std::string InLocalStorage::name() const { return "InLocalStorage"; }
7✔
28

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

33
    tile_info_ = TileInfo{};
22✔
34

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

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

42
    // Criterion: Container must be Array or Pointer (not Scalar)
43
    if (type.type_id() != types::TypeID::Pointer && type.type_id() != types::TypeID::Array) {
22✔
44
        return false;
1✔
45
    }
1✔
46

47
    // Criterion: Container must be used in the loop body
48
    auto& users = analysis_manager.get<analysis::Users>();
21✔
49
    analysis::UsersView body_users(users, body);
21✔
50
    if (body_users.uses(this->container_).empty()) {
21✔
51
        return false;
2✔
52
    }
2✔
53

54
    // Criterion: Container must be read-only within the loop (no writes)
55
    if (!body_users.writes(this->container_).empty()) {
19✔
56
        return false;
1✔
57
    }
1✔
58

59
    // Use MemoryLayoutAnalysis tile API
60
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
18✔
61
    auto* tile = mla.tile(loop_, this->container_);
18✔
62
    if (!tile) {
18✔
63
        return false;
×
64
    }
×
65

66
    // Get overapproximated extents (integer upper bounds)
67
    auto extents = tile->extents_approx();
18✔
68
    if (extents.empty()) {
18✔
NEW
69
        return false;
×
UNCOV
70
    }
×
71

72
    // Criterion: All extents must be provably integer
73
    for (auto& ext : extents) {
29✔
74
        if (!SymEngine::is_a<SymEngine::Integer>(*ext)) {
29✔
NEW
75
            return false;
×
UNCOV
76
        }
×
77
    }
29✔
78

79
    // Store tile info
80
    tile_info_.dimensions = extents;
18✔
81
    tile_info_.bases = tile->min_subset;
18✔
82
    tile_info_.strides =
18✔
83
        std::vector<symbolic::Expression>(tile->layout.strides().begin(), tile->layout.strides().end());
18✔
84
    tile_info_.offset = tile->layout.offset();
18✔
85

86
    return true;
18✔
87
}
18✔
88

89
void InLocalStorage::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
10✔
90
    auto& sdfg = builder.subject();
10✔
91
    auto& users = analysis_manager.get<analysis::Users>();
10✔
92
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
10✔
93

94
    auto parent_node = scope_analysis.parent_scope(&loop_);
10✔
95
    auto parent = dynamic_cast<structured_control_flow::Sequence*>(parent_node);
10✔
96
    if (!parent) {
10✔
97
        throw InvalidSDFGException("InLocalStorage: Parent of loop must be a Sequence!");
×
98
    }
×
99

100
    // Get type information
101
    auto& type = sdfg.type(this->container_);
10✔
102
    types::Scalar scalar_type(type.primitive_type());
10✔
103

104
    // Create local buffer name
105
    local_name_ = "__daisy_in_local_storage_" + this->container_;
10✔
106

107
    // Collect varying dimensions (extent > 1) and compute buffer layout
108
    std::vector<size_t> varying_dims;
10✔
109
    std::vector<symbolic::Expression> dim_sizes;
10✔
110
    for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
27✔
111
        auto& dim_size = tile_info_.dimensions.at(d);
17✔
112
        if (!symbolic::eq(dim_size, symbolic::integer(1))) {
17✔
113
            varying_dims.push_back(d);
17✔
114
            dim_sizes.push_back(dim_size);
17✔
115
        }
17✔
116
    }
17✔
117

118
    // Compute total buffer size
119
    symbolic::Expression total_size = symbolic::integer(1);
10✔
120
    for (auto& ds : dim_sizes) {
17✔
121
        total_size = symbolic::mul(total_size, ds);
17✔
122
    }
17✔
123

124
    // Create the local buffer
125
    types::Array buffer_type(scalar_type, total_size);
10✔
126
    builder.add_container(local_name_, buffer_type);
10✔
127

128
    // Helper: build linearized local index from per-dimension indices
129
    auto linearize = [&](const std::vector<symbolic::Symbol>& indvars) -> symbolic::Expression {
10✔
130
        symbolic::Expression linear_idx = symbolic::integer(0);
10✔
131
        symbolic::Expression stride = symbolic::integer(1);
10✔
132
        for (int i = indvars.size() - 1; i >= 0; i--) {
27✔
133
            linear_idx = symbolic::add(linear_idx, symbolic::mul(indvars[i], stride));
17✔
134
            stride = symbolic::mul(stride, dim_sizes[i]);
17✔
135
        }
17✔
136
        return linear_idx;
10✔
137
    };
10✔
138

139
    // Helper: build source subset (base[d] + copy_indvar[d]) for original container
140
    // For Pointer types: re-linearize to a single expression using layout strides
141
    // For Array types: produce multi-dimensional subset
142
    bool is_pointer = (type.type_id() == types::TypeID::Pointer);
10✔
143
    auto build_original_subset = [&](const std::vector<symbolic::Symbol>& copy_indvars) -> data_flow::Subset {
10✔
144
        std::vector<symbolic::Expression> full_indices;
10✔
145
        size_t var_idx = 0;
10✔
146
        for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
27✔
147
            if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
17✔
148
                full_indices.push_back(symbolic::add(tile_info_.bases.at(d), copy_indvars.at(var_idx++)));
17✔
149
            } else {
17✔
NEW
150
                full_indices.push_back(tile_info_.bases.at(d));
×
NEW
151
            }
×
152
        }
17✔
153

154
        if (is_pointer) {
10✔
155
            // Linearize: offset + sum(stride[d] * index[d])
156
            symbolic::Expression linear = tile_info_.offset;
10✔
157
            for (size_t d = 0; d < full_indices.size(); d++) {
27✔
158
                linear = symbolic::add(linear, symbolic::mul(tile_info_.strides.at(d), full_indices.at(d)));
17✔
159
            }
17✔
160
            return {linear};
10✔
161
        } else {
10✔
NEW
162
            return data_flow::Subset(full_indices.begin(), full_indices.end());
×
UNCOV
163
        }
×
164
    };
10✔
165

166
    // ==================================================================
167
    // Create COPY-IN loops (copy from container to local) - before target loop
168
    // ==================================================================
169
    {
10✔
170
        std::vector<symbolic::Symbol> copy_indvars;
10✔
171
        structured_control_flow::Sequence* copy_scope = parent;
10✔
172
        bool first_copy_loop = true;
10✔
173

174
        for (size_t i = 0; i < varying_dims.size(); i++) {
27✔
175
            size_t d = varying_dims[i];
17✔
176
            auto indvar_name = "__daisy_ils_" + this->container_ + "_d" + std::to_string(d);
17✔
177
            types::Scalar indvar_type(types::PrimitiveType::UInt64);
17✔
178
            builder.add_container(indvar_name, indvar_type);
17✔
179
            auto indvar = symbolic::symbol(indvar_name);
17✔
180
            copy_indvars.push_back(indvar);
17✔
181

182
            auto init = symbolic::integer(0);
17✔
183
            auto condition = symbolic::Lt(indvar, dim_sizes[i]);
17✔
184
            auto update = symbolic::add(indvar, symbolic::integer(1));
17✔
185

186
            if (first_copy_loop) {
17✔
187
                auto& copy_loop =
10✔
188
                    builder.add_for_before(*copy_scope, loop_, indvar, condition, init, update, {}, loop_.debug_info());
10✔
189
                copy_scope = &copy_loop.root();
10✔
190
                first_copy_loop = false;
10✔
191
            } else {
10✔
192
                auto& copy_loop = builder.add_for(*copy_scope, indvar, condition, init, update, {}, loop_.debug_info());
7✔
193
                copy_scope = &copy_loop.root();
7✔
194
            }
7✔
195
        }
17✔
196

197
        // Create copy block
198
        auto& copy_block = builder.add_block(*copy_scope);
10✔
199
        auto& copy_src = builder.add_access(copy_block, this->container_);
10✔
200
        auto& copy_dst = builder.add_access(copy_block, local_name_);
10✔
201
        auto& copy_tasklet = builder.add_tasklet(copy_block, data_flow::TaskletCode::assign, "_out", {"_in"});
10✔
202

203
        auto copy_src_subset = build_original_subset(copy_indvars);
10✔
204
        data_flow::Subset copy_dst_subset = {linearize(copy_indvars)};
10✔
205

206
        builder.add_computational_memlet(copy_block, copy_src, copy_tasklet, "_in", copy_src_subset, type);
10✔
207
        builder.add_computational_memlet(copy_block, copy_tasklet, "_out", copy_dst, copy_dst_subset, buffer_type);
10✔
208
    }
10✔
209

210
    // ==================================================================
211
    // Update accesses in the main loop to use the local buffer
212
    // ==================================================================
213
    analysis::UsersView body_users(users, loop_.root());
10✔
214
    auto& mla = analysis_manager.get<analysis::MemoryLayoutAnalysis>();
10✔
215

216
    for (auto* user : body_users.uses(this->container_)) {
14✔
217
        auto element = user->element();
14✔
218
        if (auto memlet = dynamic_cast<data_flow::Memlet*>(element)) {
14✔
219
            // Use MemoryLayoutAnalysis to get the delinearized access
NEW
220
            auto* access = mla.access(*memlet);
×
NEW
221
            if (access && access->subset.size() == tile_info_.dimensions.size()) {
×
222
                // Compute local index: linearize (access[d] - base[d]) for varying dims
NEW
223
                std::vector<symbolic::Expression> local_indices;
×
NEW
224
                for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
×
NEW
225
                    if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
×
NEW
226
                        local_indices.push_back(symbolic::sub(access->subset.at(d), tile_info_.bases.at(d)));
×
NEW
227
                    }
×
NEW
228
                }
×
229

230
                // Linearize
NEW
231
                symbolic::Expression linear_idx = symbolic::integer(0);
×
NEW
232
                symbolic::Expression stride = symbolic::integer(1);
×
NEW
233
                for (int i = local_indices.size() - 1; i >= 0; i--) {
×
NEW
234
                    linear_idx = symbolic::add(linear_idx, symbolic::mul(local_indices[i], stride));
×
NEW
235
                    stride = symbolic::mul(stride, dim_sizes[i]);
×
NEW
236
                }
×
237

NEW
238
                memlet->set_subset({linear_idx});
×
NEW
239
                memlet->set_base_type(buffer_type);
×
NEW
240
            } else {
×
241
                // Fallback: subtract bases from raw subset
NEW
242
                auto& old_subset = memlet->subset();
×
NEW
243
                if (old_subset.size() == tile_info_.dimensions.size()) {
×
NEW
244
                    std::vector<symbolic::Expression> local_indices;
×
NEW
245
                    for (size_t d = 0; d < tile_info_.dimensions.size(); d++) {
×
NEW
246
                        if (!symbolic::eq(tile_info_.dimensions.at(d), symbolic::integer(1))) {
×
NEW
247
                            local_indices.push_back(symbolic::sub(old_subset.at(d), tile_info_.bases.at(d)));
×
NEW
248
                        }
×
NEW
249
                    }
×
250

NEW
251
                    symbolic::Expression linear_idx = symbolic::integer(0);
×
NEW
252
                    symbolic::Expression stride = symbolic::integer(1);
×
NEW
253
                    for (int i = local_indices.size() - 1; i >= 0; i--) {
×
NEW
254
                        linear_idx = symbolic::add(linear_idx, symbolic::mul(local_indices[i], stride));
×
NEW
255
                        stride = symbolic::mul(stride, dim_sizes[i]);
×
NEW
256
                    }
×
257

NEW
258
                    memlet->set_subset({linear_idx});
×
NEW
259
                    memlet->set_base_type(buffer_type);
×
NEW
260
                }
×
261
            }
×
262
        }
×
263
    }
14✔
264

265
    // Replace container name in the loop body
266
    loop_.replace(symbolic::symbol(this->container_), symbolic::symbol(local_name_));
10✔
267

268
    // Cleanup
269
    analysis_manager.invalidate_all();
10✔
270

271
    passes::SequenceFusion sf_pass;
10✔
272
    passes::DeadCFGElimination dce_pass;
10✔
273
    bool applies = false;
10✔
274
    do {
10✔
275
        applies = false;
10✔
276
        applies |= dce_pass.run(builder, analysis_manager);
10✔
277
        applies |= sf_pass.run(builder, analysis_manager);
10✔
278
    } while (applies);
10✔
279
}
10✔
280

281
void InLocalStorage::to_json(nlohmann::json& j) const {
6✔
282
    std::string loop_type;
6✔
283
    if (dynamic_cast<structured_control_flow::For*>(&loop_)) {
6✔
284
        loop_type = "for";
6✔
285
    } else if (dynamic_cast<structured_control_flow::Map*>(&loop_)) {
6✔
286
        loop_type = "map";
×
287
    } else {
×
288
        throw std::runtime_error("Unsupported loop type for serialization of loop: " + loop_.indvar()->get_name());
×
289
    }
×
290
    j["subgraph"] = {
6✔
291
        {"0", {{"element_id", this->loop_.element_id()}, {"type", loop_type}}},
6✔
292
        {"1", {{"element_id", this->access_node_.element_id()}, {"type", "access_node"}}}
6✔
293
    };
6✔
294
    j["transformation_type"] = this->name();
6✔
295
    j["container"] = container_;
6✔
296
}
6✔
297

298
InLocalStorage InLocalStorage::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
1✔
299
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
1✔
300
    auto element = builder.find_element_by_id(loop_id);
1✔
301
    if (!element) {
1✔
302
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
303
    }
×
304
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
1✔
305
    if (!loop) {
1✔
306
        throw InvalidTransformationDescriptionException(
×
307
            "Element with ID " + std::to_string(loop_id) + " is not a structured loop."
×
308
        );
×
309
    }
×
310

311
    auto access_node = dynamic_cast<
1✔
312
        data_flow::AccessNode*>(builder.find_element_by_id(desc.at("subgraph").at("1").at("element_id").get<size_t>()));
1✔
313
    if (!access_node) {
1✔
314
        throw InvalidTransformationDescriptionException(
×
315
            "Access node with ID " + std::to_string(desc.at("subgraph").at("1").at("element_id").get<size_t>()) +
×
316
            " not found."
×
317
        );
×
318
    }
×
319

320
    return InLocalStorage(*loop, *access_node);
1✔
321
}
1✔
322

323
} // namespace transformations
324
} // 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