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

daisytuner / docc / 28106147644

24 Jun 2026 02:32PM UTC coverage: 61.922% (+0.1%) from 61.779%
28106147644

Pull #806

github

web-flow
Merge 2be414d54 into 57cc1db99
Pull Request #806: Map Collapse for Multiple targets in a neste sequence

165 of 185 new or added lines in 2 files covered. (89.19%)

419 existing lines in 30 files now uncovered.

37705 of 60891 relevant lines covered (61.92%)

1004.4 hits per line

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

90.17
/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/users.h"
6
#include "sdfg/deepcopy/structured_sdfg_deep_copy.h"
7

8
namespace sdfg {
9
namespace transformations {
10

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

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

18
std::string LoopDistribute::name() const { return "LoopDistribute"; };
6✔
19

20
namespace {
21

22
// Walk parent_scope chain from `user`'s scope up; return true if `subtree` is
23
// reached. Mirrors LoopCarriedDependencyAnalysis::pairs_between's containment.
24
bool user_in_subtree(analysis::User* user, const structured_control_flow::ControlFlowNode& subtree) {
4,062✔
25
    if (user->element() == nullptr) {
4,062✔
26
        return false;
×
27
    }
×
28
    auto* scope = analysis::Users::scope(user);
4,062✔
29
    while (scope != nullptr) {
18,622✔
30
        if (scope == &subtree) return true;
16,789✔
31
        scope = scope->get_parent();
14,560✔
32
    }
14,560✔
33
    return false;
1,833✔
34
}
4,062✔
35

36
} // namespace
37

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

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

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

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

94
    auto indvar_name = this->loop_.indvar()->get_name();
60✔
95

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

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

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

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

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

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

200
    return true;
46✔
201
};
48✔
202

203
void LoopDistribute::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
45✔
204
    auto& sdfg = builder.subject();
45✔
205

206
    auto indvar = this->loop_.indvar();
45✔
207
    auto condition = this->loop_.condition();
45✔
208
    auto update = this->loop_.update();
45✔
209
    auto init = this->loop_.init();
45✔
210

211
    auto parent = static_cast<structured_control_flow::Sequence*>(this->loop_.get_parent());
45✔
212

213
    structured_control_flow::ScheduleType schedule_type = structured_control_flow::ScheduleType_Sequential::create();
45✔
214
    if (auto map_stmt = dynamic_cast<structured_control_flow::Map*>(&this->loop_)) {
45✔
215
        schedule_type = map_stmt->schedule_type();
34✔
216
    }
34✔
217

218
    auto& body = this->loop_.root();
45✔
219

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

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

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

261
    analysis_manager.invalidate_all();
45✔
262
};
45✔
263

264
void LoopDistribute::to_json(nlohmann::json& j) const {
3✔
265
    j["transformation_type"] = this->name();
3✔
266
    j["parameters"] = nlohmann::json::object();
3✔
267

268
    serializer::JSONSerializer ser_flat(false);
3✔
269
    j["subgraph"] = nlohmann::json::object();
3✔
270
    j["subgraph"]["0"] = nlohmann::json::object();
3✔
271
    ser_flat.serialize_node(j["subgraph"]["0"], loop_);
3✔
272
};
3✔
273

274
LoopDistribute LoopDistribute::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
2✔
275
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
2✔
276
    auto element = builder.find_element_by_id(loop_id);
2✔
277
    if (element == nullptr) {
2✔
UNCOV
278
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
UNCOV
279
    }
×
280
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
2✔
281
    if (loop == nullptr) {
2✔
UNCOV
282
        throw InvalidTransformationDescriptionException(
×
283
            "Element with ID " + std::to_string(loop_id) + " is not a StructuredLoop."
×
284
        );
×
UNCOV
285
    }
×
286

287
    return LoopDistribute(*loop);
2✔
288
};
2✔
289

290
} // namespace transformations
291
} // 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