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

daisytuner / docc / 25459771247

06 May 2026 08:37PM UTC coverage: 65.659% (+0.4%) from 65.223%
25459771247

push

github

web-flow
Merge pull request #704 from daisytuner/lcd-analysis

Refactors LoopCarriedDependencyAnalysis into standalone analysis

731 of 828 new or added lines in 13 files covered. (88.29%)

42 existing lines in 5 files now uncovered.

32175 of 49003 relevant lines covered (65.66%)

12934.86 hits per line

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

90.4
/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
    auto* scope = analysis::Users::scope(user);
4,057✔
31
    while (scope != nullptr) {
18,533✔
32
        if (scope == &subtree) return true;
16,705✔
33
        scope = scope_analysis.parent_scope(scope);
14,476✔
34
    }
14,476✔
35
    return false;
1,828✔
36
}
4,057✔
37

38
} // namespace
39

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

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

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

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

97
    auto indvar_name = this->loop_.indvar()->get_name();
60✔
98

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

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

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

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

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

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

203
    return true;
46✔
204
};
48✔
205

206
void LoopDistribute::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
45✔
207
    auto& sdfg = builder.subject();
45✔
208

209
    auto indvar = this->loop_.indvar();
45✔
210
    auto condition = this->loop_.condition();
45✔
211
    auto update = this->loop_.update();
45✔
212
    auto init = this->loop_.init();
45✔
213

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

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

222
    auto& body = this->loop_.root();
45✔
223

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

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

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

265
    analysis_manager.invalidate_all();
45✔
266
};
45✔
267

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

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

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

296
    return LoopDistribute(*loop);
2✔
297
};
2✔
298

299
} // namespace transformations
300
} // 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