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

daisytuner / docc / 25501744090

07 May 2026 02:21PM UTC coverage: 65.649%. First build
25501744090

Pull #681

github

web-flow
Merge 444892749 into 8010510fa
Pull Request #681: Target specific expand

58 of 71 new or added lines in 5 files covered. (81.69%)

32178 of 49015 relevant lines covered (65.65%)

12931.86 hits per line

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

89.44
/opt/src/transformations/loop_distribute.cpp
1
#include "sdfg/transformations/loop_distribute.h"
2

3
#include "sdfg/analysis/data_dependency_analysis.h"
4
#include "sdfg/analysis/loop_carried_dependency_analysis.h"
5
#include "sdfg/analysis/scope_analysis.h"
6
#include "sdfg/analysis/users.h"
7
#include "sdfg/deepcopy/structured_sdfg_deep_copy.h"
8

9
namespace sdfg {
10
namespace transformations {
11

12
LoopDistribute::
13
    LoopDistribute(structured_control_flow::StructuredLoop& loop, structured_control_flow::ControlFlowNode& child)
14
    : loop_(loop), child_(child) {};
108✔
15

16
LoopDistribute::LoopDistribute(structured_control_flow::StructuredLoop& loop)
17
    : LoopDistribute(loop, loop.root().at(0).first) {};
106✔
18

19
std::string LoopDistribute::name() const { return "LoopDistribute"; };
9✔
20

21
namespace {
22

23
// Walk parent_scope chain from `user`'s scope up; return true if `subtree` is
24
// reached. Mirrors LoopCarriedDependencyAnalysis::pairs_between's containment.
25
bool user_in_subtree(
26
    analysis::User* user,
27
    const structured_control_flow::ControlFlowNode& subtree,
28
    analysis::ScopeAnalysis& scope_analysis
29
) {
4,057✔
30
    if (user->element() == nullptr) {
4,057✔
NEW
31
        return false;
×
NEW
32
    }
×
33
    auto* scope = analysis::Users::scope(user);
4,057✔
34
    while (scope != nullptr) {
18,533✔
35
        if (scope == &subtree) return true;
16,705✔
36
        scope = scope_analysis.parent_scope(scope);
14,476✔
37
    }
14,476✔
38
    return false;
1,828✔
39
}
4,057✔
40

41
} // namespace
42

43
bool LoopDistribute::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
61✔
44
    // Criterion: Loop must have at least 2 children to distribute.
45
    auto& body = this->loop_.root();
61✔
46
    if (body.size() < 2) {
61✔
47
        return false;
1✔
48
    }
1✔
49

50
    // Criterion: child_ must be in loop body; record its index.
51
    size_t child_idx = body.size();
60✔
52
    for (size_t i = 0; i < body.size(); i++) {
64✔
53
        if (&body.at(i).first == &this->child_) {
64✔
54
            child_idx = i;
60✔
55
            break;
60✔
56
        }
60✔
57
    }
64✔
58
    if (child_idx == body.size()) {
60✔
59
        return false;
×
60
    }
×
61

62
    // Criterion: Transitions must not have assignments.
63
    for (size_t i = 0; i < body.size(); i++) {
212✔
64
        auto& transition = body.at(i).second;
152✔
65
        if (!transition.assignments().empty()) {
152✔
66
            return false;
×
67
        }
×
68
    }
152✔
69

70
    // Criterion: subset-aware loop-carried dependence check.
71
    //
72
    // apply() performs an in-place 3-way split of `loop_` preserving program
73
    // order:
74
    //     prefix loop  : children [0 .. child_idx)
75
    //     center loop  : { child_ }   (the original loop_)
76
    //     suffix loop  : children (child_idx .. body.size())
77
    // Empty pieces are not materialized.
78
    //
79
    // For correctness we examine each loop-carried (writer, reader) pair from
80
    // LoopCarriedDependencyAnalysis. After the split, the body is partitioned
81
    // into THREE destination groups (prefix / center / suffix) — note that
82
    // siblings inside the same destination group remain inside ONE shared
83
    // loop body, so a (writer, reader) pair whose users land in the same
84
    // destination group is NOT cross-loop after distribution and survives
85
    // unchanged. Only pairs that straddle two distinct destination groups
86
    // get split apart.
87
    //
88
    // For pairs that straddle destination groups:
89
    //   - WAW pairs are always safe: program-order between groups is
90
    //     preserved, so the surviving last-writer to any cell is unchanged.
91
    //   - RAW pairs are safe iff writer and reader stay in the same group.
92
    //     A cross-group RAW would force the reader to see a different write
93
    //     after the groups are serialized.
94
    auto& lcd = analysis_manager.get<analysis::LoopCarriedDependencyAnalysis>();
60✔
95
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
60✔
96
    if (!lcd.available(this->loop_)) {
60✔
97
        return false;
×
98
    }
×
99

100
    auto indvar_name = this->loop_.indvar()->get_name();
60✔
101

102
    // Classify a user into its piece (direct child index in body) and from
103
    // there into its destination group: 0 = prefix, 1 = center, 2 = suffix.
104
    auto piece_of = [&](analysis::User* u) -> size_t {
2,229✔
105
        for (size_t i = 0; i < body.size(); i++) {
4,057✔
106
            if (user_in_subtree(u, body.at(i).first, scope_analysis)) {
4,057✔
107
                return i;
2,229✔
108
            }
2,229✔
109
        }
4,057✔
110
        return body.size();
×
111
    };
2,229✔
112
    auto group_of = [&](size_t piece) -> int {
1,032✔
113
        if (piece == body.size()) return -1;
1,032✔
114
        if (piece < child_idx) return 0; // prefix
1,032✔
115
        if (piece == child_idx) return 1; // center
928✔
116
        return 2; // suffix
566✔
117
    };
928✔
118

119
    // (1) Cross-iteration loop-carried pairs (from LCDA).
120
    std::unordered_set<std::string> lc_containers;
60✔
121
    for (auto& pair : lcd.pairs(this->loop_)) {
764✔
122
        lc_containers.insert(pair.writer->container());
764✔
123
    }
764✔
124
    for (auto& pair : lcd.pairs(this->loop_)) {
513✔
125
        const std::string& container = pair.writer->container();
513✔
126
        if (container == indvar_name) continue;
513✔
127
        if (pair.deltas.empty) continue; // defensive; pairs() should not store empties
513✔
128

129
        size_t w_piece = piece_of(pair.writer);
513✔
130
        size_t r_piece = piece_of(pair.reader);
513✔
131
        if (w_piece == body.size() || r_piece == body.size()) {
513✔
132
            // User not localizable to a direct child piece -- be conservative.
133
            return false;
×
134
        }
×
135
        int w_group = group_of(w_piece);
513✔
136
        int r_group = group_of(r_piece);
513✔
137
        if (w_group == r_group) continue; // intra-group, distribution preserves it
513✔
138

139
        // Cross-group pair.
140
        if (pair.type == analysis::LoopCarriedDependency::LOOP_CARRIED_DEPENDENCY_WRITE_WRITE) {
36✔
141
            continue; // WAW always safe under program-order-preserving split
24✔
142
        }
24✔
143

144
        // Cross-group RAW: unsafe. After distribution the writer's piece runs
145
        // to completion across all iterations before the reader's piece, so
146
        // the reader sees a different (later) value than in the fused order.
147
        return false;
12✔
148
    }
36✔
149

150
    // (2) Intra-iteration cross-piece RAW on scalars (from DDA).
151
    //
152
    // LCDA captures only loop-carried (cross-iteration) flow. A reader that
153
    // is satisfied within the same iteration by a writer in a different piece
154
    // becomes loop-carried after distribution: the writer's piece runs all
155
    // iterations before the reader's piece.
156
    //
157
    // For array containers this is generally safe: if no LC pair exists on
158
    // the container the writer's per-iter footprint is disjoint, so the
159
    // intra-iter cell flow is preserved. If LC pairs DO exist on the array,
160
    // LCDA still distinguishes which specific (writer, reader) pairs flow
161
    // across iterations via delta-set computation, so the LC pass above
162
    // catches the unsafe ones.
163
    //
164
    // For SCALAR containers (empty subset) every access is to the same single
165
    // cell. If the scalar has any LC dependence in this loop (i.e. it is
166
    // overwritten / re-read across iters rather than being a loop-invariant
167
    // constant), an intra-iter cross-piece RAW becomes unsafe under
168
    // distribution: the reader's piece would see the writer's last-iteration
169
    // value instead of the matching iteration's value. DDA's reaching-defs
170
    // also drop these cross-piece reads from `ue_reads` (they are killed
171
    // intra-iter by the same-iter writer in the middle piece), so LCDA
172
    // never sees them as loop-carried -- we must catch them directly.
173
    auto& dda = analysis_manager.get<analysis::DataDependencyAnalysis>();
48✔
174
    auto& users = analysis_manager.get<analysis::Users>();
48✔
175
    analysis::UsersView body_view(users, this->loop_.root());
48✔
176
    for (auto* writer : body_view.writes()) {
368✔
177
        if (writer->container() == indvar_name) continue;
368✔
178
        if (lc_containers.find(writer->container()) == lc_containers.end()) {
368✔
179
            continue; // no LC dep on this container
114✔
180
        }
114✔
181
        if (!writer->subsets().empty()) {
254✔
182
            // Array-typed access: per-pair LC analysis above is precise enough.
183
            bool any_nonempty = false;
254✔
184
            for (auto& s : writer->subsets()) {
254✔
185
                if (!s.empty()) {
254✔
186
                    any_nonempty = true;
14✔
187
                    break;
14✔
188
                }
14✔
189
            }
254✔
190
            if (any_nonempty) continue;
254✔
191
        }
254✔
192
        size_t w_piece = piece_of(writer);
240✔
193
        if (w_piece == body.size()) continue;
240✔
194
        for (auto* reader : dda.defines(*writer)) {
963✔
195
            size_t r_piece = piece_of(reader);
963✔
196
            if (r_piece == body.size()) continue;
963✔
197
            if (w_piece == r_piece) continue;
963✔
198
            int w_group = group_of(w_piece);
3✔
199
            int r_group = group_of(r_piece);
3✔
200
            if (w_group == r_group) continue;
3✔
201
            // Cross-group intra-iter scalar RAW: unsafe under distribution.
202
            return false;
2✔
203
        }
3✔
204
    }
240✔
205

206
    return true;
46✔
207
};
48✔
208

209
void LoopDistribute::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
45✔
210
    auto& sdfg = builder.subject();
45✔
211

212
    auto indvar = this->loop_.indvar();
45✔
213
    auto condition = this->loop_.condition();
45✔
214
    auto update = this->loop_.update();
45✔
215
    auto init = this->loop_.init();
45✔
216

217
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
45✔
218
    auto parent = static_cast<structured_control_flow::Sequence*>(scope_analysis.parent_scope(&this->loop_));
45✔
219

220
    structured_control_flow::ScheduleType schedule_type = structured_control_flow::ScheduleType_Sequential::create();
45✔
221
    if (auto map_stmt = dynamic_cast<structured_control_flow::Map*>(&this->loop_)) {
45✔
222
        schedule_type = map_stmt->schedule_type();
34✔
223
    }
34✔
224

225
    auto& body = this->loop_.root();
45✔
226

227
    // Locate child_'s current index (guaranteed present by can_be_applied).
228
    size_t child_idx = body.size();
45✔
229
    for (size_t i = 0; i < body.size(); i++) {
47✔
230
        if (&body.at(i).first == &this->child_) {
47✔
231
            child_idx = i;
45✔
232
            break;
45✔
233
        }
45✔
234
    }
47✔
235

236
    // Create suffix loop FIRST (added after `loop_`) so prefix creation does
237
    // not shift body indices we still need.
238
    if (child_idx + 1 < body.size()) {
45✔
239
        auto& suffix_loop = builder.add_map_after(
44✔
240
            *parent, this->loop_, indvar, condition, init, update, schedule_type, {}, this->loop_.debug_info()
44✔
241
        );
44✔
242
        // Move all children after child_ into the suffix loop, preserving
243
        // their relative order. Each move shifts subsequent indices down by 1.
244
        size_t to_move = body.size() - (child_idx + 1);
44✔
245
        for (size_t i = 0; i < to_move; i++) {
104✔
246
            builder.move_child(body, child_idx + 1, suffix_loop.root());
60✔
247
        }
60✔
248
        std::string suffix_indvar = builder.find_new_name(indvar->get_name());
44✔
249
        builder.add_container(suffix_indvar, sdfg.type(indvar->get_name()));
44✔
250
        suffix_loop.replace(indvar, symbolic::symbol(suffix_indvar));
44✔
251
    }
44✔
252

253
    // Create prefix loop (added before `loop_`).
254
    if (child_idx > 0) {
45✔
255
        auto& prefix_loop = builder.add_map_before(
1✔
256
            *parent, this->loop_, indvar, condition, init, update, schedule_type, {}, this->loop_.debug_info()
1✔
257
        );
1✔
258
        // Move all children before child_ into the prefix loop. Always move
259
        // child at index 0 since each move shifts subsequent indices down.
260
        for (size_t i = 0; i < child_idx; i++) {
3✔
261
            builder.move_child(body, 0, prefix_loop.root());
2✔
262
        }
2✔
263
        std::string prefix_indvar = builder.find_new_name(indvar->get_name());
1✔
264
        builder.add_container(prefix_indvar, sdfg.type(indvar->get_name()));
1✔
265
        prefix_loop.replace(indvar, symbolic::symbol(prefix_indvar));
1✔
266
    }
1✔
267

268
    analysis_manager.invalidate_all();
45✔
269
};
45✔
270

271
void LoopDistribute::to_json(nlohmann::json& j) const {
3✔
272
    std::string loop_type;
3✔
273
    if (dynamic_cast<structured_control_flow::For*>(&loop_)) {
3✔
274
        loop_type = "for";
2✔
275
    } else if (dynamic_cast<structured_control_flow::Map*>(&loop_)) {
2✔
276
        loop_type = "map";
1✔
277
    } else {
1✔
278
        throw std::runtime_error("Unsupported loop type for serialization of loop: " + loop_.indvar()->get_name());
×
279
    }
×
280

281
    j["transformation_type"] = this->name();
3✔
282
    j["subgraph"] = {{"0", {{"element_id", this->loop_.element_id()}, {"type", loop_type}}}};
3✔
283
    j["transformation_type"] = this->name();
3✔
284
};
3✔
285

286
LoopDistribute LoopDistribute::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
2✔
287
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
2✔
288
    auto element = builder.find_element_by_id(loop_id);
2✔
289
    if (element == nullptr) {
2✔
290
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
291
    }
×
292
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
2✔
293
    if (loop == nullptr) {
2✔
294
        throw InvalidTransformationDescriptionException(
×
295
            "Element with ID " + std::to_string(loop_id) + " is not a StructuredLoop."
×
296
        );
×
297
    }
×
298

299
    return LoopDistribute(*loop);
2✔
300
};
2✔
301

302
} // namespace transformations
303
} // 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