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

daisytuner / docc / 28101952530

24 Jun 2026 01:27PM UTC coverage: 61.988% (+0.1%) from 61.844%
28101952530

Pull #802

github

web-flow
Merge 54d7d8794 into 57cc1db99
Pull Request #802: adds reduce as new structured loop type

604 of 780 new or added lines in 24 files covered. (77.44%)

96 existing lines in 11 files now uncovered.

38005 of 61310 relevant lines covered (61.99%)

1006.39 hits per line

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

0.0
/opt/src/transformations/loop_peeling.cpp
1
#include "sdfg/transformations/loop_peeling.h"
2

3
#include "sdfg/builder/structured_sdfg_builder.h"
4
#include "sdfg/deepcopy/structured_sdfg_deep_copy.h"
5
#include "sdfg/structured_control_flow/for.h"
6
#include "sdfg/structured_control_flow/map.h"
7
#include "sdfg/structured_control_flow/sequence.h"
8
#include "sdfg/symbolic/symbolic.h"
9

10
#include <symengine/functions.h>
11
#include <symengine/logic.h>
12

13
namespace sdfg {
14
namespace transformations {
15

16
LoopPeeling::LoopPeeling(structured_control_flow::StructuredLoop& loop) : loop_(loop) {};
×
17

18
std::string LoopPeeling::name() const { return "LoopPeeling"; };
×
19

20
/// Extract upper bound from a condition of the form `indvar < bound`.
21
/// Returns SymEngine::null if not a simple strict-less-than on indvar.
22
static symbolic::Expression extract_upper_bound(const symbolic::Condition& cond, const symbolic::Symbol& indvar) {
×
23
    if (!SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
×
24
        return SymEngine::null;
×
25
    }
×
26
    auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
×
27
    auto lhs = lt->get_arg1();
×
28
    auto rhs = lt->get_arg2();
×
29
    if (symbolic::eq(lhs, indvar)) {
×
30
        return rhs;
×
31
    }
×
32
    return SymEngine::null;
×
33
}
×
34

35
/// Determine if `bound - init` simplifies to a positive integer constant.
36
/// Also handles the case where init is a max() expression: if bound - any_arg
37
/// gives a positive integer constant, the loop has a bounded trip count.
38
static bool is_constant_trip_bound(const symbolic::Expression& bound, const symbolic::Expression& init) {
×
39
    auto diff = symbolic::expand(symbolic::sub(bound, init));
×
40
    if (SymEngine::is_a<SymEngine::Integer>(*diff)) {
×
41
        auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(diff);
×
42
        return val->as_int() > 0;
×
43
    }
×
44
    // If init is max(a, b, ...), check if bound - arg is constant for any arg.
45
    // trip_count = bound - max(a, b, ...) = min(bound-a, bound-b, ...)
46
    // If any (bound - arg) is a positive constant, then the trip count is at most that constant.
47
    if (SymEngine::is_a<SymEngine::Max>(*init)) {
×
48
        auto max_op = SymEngine::rcp_static_cast<const SymEngine::Max>(init);
×
49
        auto args = max_op->get_args();
×
50
        for (auto& arg : args) {
×
51
            auto arg_diff = symbolic::expand(symbolic::sub(bound, arg));
×
52
            if (SymEngine::is_a<SymEngine::Integer>(*arg_diff)) {
×
53
                auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(arg_diff);
×
54
                if (val->as_int() > 0) {
×
55
                    return true;
×
56
                }
×
57
            }
×
58
        }
×
59
    }
×
60
    return false;
×
61
}
×
62

63
/// Get the constant trip count for a bound/init pair that passes is_constant_trip_bound.
64
/// Returns the positive integer trip count, handling max() in init.
65
static int64_t get_constant_trip_count(const symbolic::Expression& bound, const symbolic::Expression& init) {
×
66
    auto diff = symbolic::expand(symbolic::sub(bound, init));
×
67
    if (SymEngine::is_a<SymEngine::Integer>(*diff)) {
×
68
        return SymEngine::rcp_static_cast<const SymEngine::Integer>(diff)->as_int();
×
69
    }
×
70
    // For init = max(a, b, ...), find the smallest positive constant among (bound - arg)
71
    if (SymEngine::is_a<SymEngine::Max>(*init)) {
×
72
        auto max_op = SymEngine::rcp_static_cast<const SymEngine::Max>(init);
×
73
        auto args = max_op->get_args();
×
74
        int64_t best = INT64_MAX;
×
75
        for (auto& arg : args) {
×
76
            auto arg_diff = symbolic::expand(symbolic::sub(bound, arg));
×
77
            if (SymEngine::is_a<SymEngine::Integer>(*arg_diff)) {
×
78
                auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(arg_diff)->as_int();
×
79
                if (val > 0 && val < best) {
×
80
                    best = val;
×
81
                }
×
82
            }
×
83
        }
×
84
        return best;
×
85
    }
×
86
    // Should not reach here if is_constant_trip_bound returned true
87
    return 0;
×
88
}
×
89

90
/// Check if a loop has a compound condition with at least one canonical bound.
91
static bool loop_is_peelable(structured_control_flow::StructuredLoop& loop) {
×
92
    auto cond = loop.condition();
×
93
    if (!SymEngine::is_a<SymEngine::And>(*cond)) {
×
94
        return false;
×
95
    }
×
96
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
97
    auto& conjuncts = and_cond->get_container();
×
98
    if (conjuncts.size() < 2) {
×
99
        return false;
×
100
    }
×
101

102
    auto indvar = loop.indvar();
×
103
    auto init = loop.init();
×
104
    bool has_canonical = false;
×
105
    bool has_dynamic = false;
×
106

107
    for (auto& conjunct : conjuncts) {
×
108
        auto bound = extract_upper_bound(conjunct, indvar);
×
109
        if (bound == SymEngine::null) {
×
110
            return false;
×
111
        }
×
112
        if (is_constant_trip_bound(bound, init)) {
×
113
            has_canonical = true;
×
114
        } else {
×
115
            has_dynamic = true;
×
116
        }
×
117
    }
×
118
    return has_canonical && has_dynamic;
×
119
}
×
120

121
/// For a peelable loop, extract the canonical bound (tightest constant-trip bound).
122
static symbolic::Expression find_canonical_bound(structured_control_flow::StructuredLoop& loop) {
×
123
    auto cond = loop.condition();
×
124
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
125
    auto& conjuncts = and_cond->get_container();
×
126
    auto indvar = loop.indvar();
×
127
    auto init = loop.init();
×
128

129
    symbolic::Expression canonical = SymEngine::null;
×
130
    int64_t canonical_trip = INT64_MAX;
×
131
    for (auto& conjunct : conjuncts) {
×
132
        auto bound = extract_upper_bound(conjunct, indvar);
×
133
        if (bound == SymEngine::null) continue;
×
134
        if (!is_constant_trip_bound(bound, init)) continue;
×
135

136
        auto trip = get_constant_trip_count(bound, init);
×
137
        if (canonical == SymEngine::null || trip < canonical_trip) {
×
138
            canonical = bound;
×
139
            canonical_trip = trip;
×
140
        }
×
141
    }
×
142
    return canonical;
×
143
}
×
144

145
/// Build the peeling condition for a single loop: canonical_bound <= each dynamic bound.
146
static symbolic::Condition build_loop_peeling_condition(
147
    structured_control_flow::StructuredLoop& loop, const symbolic::Expression& canonical_bound
148
) {
×
149
    auto cond = loop.condition();
×
150
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
151
    auto& conjuncts = and_cond->get_container();
×
152
    auto indvar = loop.indvar();
×
153
    auto init = loop.init();
×
154

155
    symbolic::Condition result = SymEngine::boolTrue;
×
156
    for (auto& conjunct : conjuncts) {
×
157
        auto bound = extract_upper_bound(conjunct, indvar);
×
158
        if (bound == SymEngine::null) continue;
×
159
        if (is_constant_trip_bound(bound, init) && symbolic::eq(bound, canonical_bound)) continue;
×
160
        // canonical_bound <= this dynamic/looser bound
161
        result = symbolic::And(result, symbolic::Le(canonical_bound, bound));
×
162
    }
×
163

164
    // If init is max(a, b, ...), we need to ensure the max resolves to the arg
165
    // that gives the constant trip count. Add conditions: chosen_arg >= other_args.
166
    if (SymEngine::is_a<SymEngine::Max>(*init)) {
×
167
        auto max_op = SymEngine::rcp_static_cast<const SymEngine::Max>(init);
×
168
        auto args = max_op->get_args();
×
169
        // Find the arg that gives the constant trip (smallest constant trip)
170
        symbolic::Expression chosen_arg = SymEngine::null;
×
171
        int64_t best_trip = INT64_MAX;
×
172
        for (auto& arg : args) {
×
173
            auto arg_diff = symbolic::expand(symbolic::sub(canonical_bound, arg));
×
174
            if (SymEngine::is_a<SymEngine::Integer>(*arg_diff)) {
×
175
                auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(arg_diff)->as_int();
×
176
                if (val > 0 && val < best_trip) {
×
177
                    best_trip = val;
×
178
                    chosen_arg = arg;
×
179
                }
×
180
            }
×
181
        }
×
182
        // Add conditions: chosen_arg >= each other arg (so max resolves to chosen_arg)
183
        if (chosen_arg != SymEngine::null) {
×
184
            for (auto& arg : args) {
×
185
                if (!symbolic::eq(arg, chosen_arg)) {
×
186
                    result = symbolic::And(result, symbolic::Le(arg, chosen_arg));
×
187
                }
×
188
            }
×
189
        }
×
190
    }
×
191

192
    return result;
×
193
}
×
194

195
/// Collect the perfectly nested chain of peelable loops starting from `loop`.
196
/// A chain continues as long as the loop body has exactly one child which is
197
/// another peelable structured loop.
198
static std::vector<structured_control_flow::StructuredLoop*> collect_peelable_nest(structured_control_flow::StructuredLoop&
199
                                                                                       loop) {
×
200
    std::vector<structured_control_flow::StructuredLoop*> nest;
×
201
    nest.push_back(&loop);
×
202

203
    auto* current = &loop;
×
204
    while (true) {
×
205
        auto& body = current->root();
×
206
        if (body.size() != 1) break;
×
207
        auto& child = body.at(0).first;
×
208
        auto* inner_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child);
×
209
        if (!inner_loop) break;
×
210
        if (!loop_is_peelable(*inner_loop)) break;
×
211
        nest.push_back(inner_loop);
×
212
        current = inner_loop;
×
213
    }
×
214
    return nest;
×
215
}
×
216

217
bool LoopPeeling::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
×
218
    return loop_is_peelable(loop_);
×
219
};
×
220

221
void LoopPeeling::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
×
222
    auto& sdfg = builder.subject();
×
223

224
    // Collect perfectly nested chain of peelable loops
225
    auto nest = collect_peelable_nest(loop_);
×
226

227
    // For each loop in the nest, find its canonical bound and build peeling condition
228
    struct LoopPeelInfo {
×
229
        structured_control_flow::StructuredLoop* loop;
×
230
        symbolic::Expression canonical_bound;
×
231
        symbolic::Condition peeling_condition;
×
232
    };
×
233
    std::vector<LoopPeelInfo> peel_infos;
×
234

235
    symbolic::Condition combined_peeling_condition = SymEngine::boolTrue;
×
236
    for (auto* loop : nest) {
×
237
        auto canonical = find_canonical_bound(*loop);
×
238
        auto peel_cond = build_loop_peeling_condition(*loop, canonical);
×
239
        combined_peeling_condition = symbolic::And(combined_peeling_condition, peel_cond);
×
240
        peel_infos.push_back({loop, canonical, peel_cond});
×
241
    }
×
242

243
    // Get parent scope of the outermost loop
244
    auto parent = static_cast<structured_control_flow::Sequence*>(loop_.get_parent());
×
245

246
    // Create IfElse before the outermost loop
247
    auto& if_else = builder.add_if_else_before(*parent, loop_, {}, loop_.debug_info());
×
248

249
    // === Then branch: all loops normalized to start at 0 with constant trip counts ===
250
    auto& then_branch = builder.add_case(if_else, combined_peeling_condition);
×
251

252
    // Build the nest of loops with clean 0-based bounds in the then branch
253
    structured_control_flow::Sequence* current_parent = &then_branch;
×
254
    // Track substitutions: original_indvar → indvar + init (for body fixup)
255
    std::vector<std::pair<symbolic::Symbol, symbolic::Expression>> substitutions;
×
256

257
    for (size_t i = 0; i < peel_infos.size(); i++) {
×
258
        auto& info = peel_infos[i];
×
259
        auto* loop = info.loop;
×
260
        auto indvar = loop->indvar();
×
261
        auto init = loop->init();
×
262

263
        // Compute constant trip count: canonical_bound - init (using helper for max() cases)
264
        auto trip_count = symbolic::integer(get_constant_trip_count(info.canonical_bound, init));
×
265

266
        // Resolve effective init for the then-branch.
267
        // If init = max(a, b, ...) and canonical_bound - b is the constant trip,
268
        // then in the then-branch (where peeling condition guarantees max = b), use b directly.
269
        symbolic::Expression effective_init = init;
×
270
        if (SymEngine::is_a<SymEngine::Max>(*init)) {
×
271
            auto max_op = SymEngine::rcp_static_cast<const SymEngine::Max>(init);
×
272
            auto args = max_op->get_args();
×
273
            int64_t best_trip = INT64_MAX;
×
274
            for (auto& arg : args) {
×
275
                auto arg_diff = symbolic::expand(symbolic::sub(info.canonical_bound, arg));
×
276
                if (SymEngine::is_a<SymEngine::Integer>(*arg_diff)) {
×
277
                    auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(arg_diff)->as_int();
×
278
                    if (val > 0 && val < best_trip) {
×
279
                        best_trip = val;
×
280
                        effective_init = arg;
×
281
                    }
×
282
                }
×
283
            }
×
284
        }
×
285

286
        // New loop: indvar goes from 0 to trip_count
287
        auto zero_condition = symbolic::Lt(indvar, trip_count);
×
288
        auto zero_init = symbolic::integer(0);
×
289

290
        structured_control_flow::StructuredLoop* new_loop = nullptr;
×
291
        if (auto map = dynamic_cast<structured_control_flow::Map*>(loop)) {
×
292
            new_loop = &builder.add_map(
×
293
                *current_parent,
×
294
                indvar,
×
295
                zero_condition,
×
296
                zero_init,
×
297
                loop->update(),
×
298
                map->schedule_type(),
×
299
                {},
×
300
                loop->debug_info()
×
301
            );
×
302
        } else {
×
303
            new_loop =
×
304
                &builder
×
305
                     .add_for(*current_parent, indvar, zero_condition, zero_init, loop->update(), {}, loop->debug_info());
×
306
        }
×
307

308
        // Record substitution: in the body, original indvar usage = new_indvar + effective_init
309
        substitutions.push_back({indvar, effective_init});
×
310
        current_parent = &new_loop->root();
×
311
    }
×
312

313
    // Deep copy the innermost loop's body into the new innermost loop
314
    auto* innermost = nest.back();
×
315
    deepcopy::StructuredSDFGDeepCopy main_copier(builder, *current_parent, innermost->root());
×
316
    main_copier.insert();
×
317

318
    // Apply shift substitutions in the copied body:
319
    // Replace indvar with (indvar + original_init) so body accesses use correct offsets.
320
    // Must apply outermost first (since inner body may reference outer indvars).
321
    for (auto& [indvar, init] : substitutions) {
×
322
        if (symbolic::eq(init, symbolic::zero())) continue;
×
323
        auto shifted_expr = symbolic::add(indvar, init);
×
UNCOV
324
        current_parent->replace(indvar, shifted_expr);
×
UNCOV
325
    }
×
326

327
    // === Else branch: original compound bounds (remainder) ===
328
    auto else_condition = symbolic::Not(combined_peeling_condition);
×
UNCOV
329
    auto& else_branch = builder.add_case(if_else, else_condition);
×
330

331
    // Build the nest of loops with original conditions in the else branch
UNCOV
332
    current_parent = &else_branch;
×
333
    for (size_t i = 0; i < peel_infos.size(); i++) {
×
334
        auto* loop = peel_infos[i].loop;
×
335

336
        structured_control_flow::StructuredLoop* new_loop = nullptr;
×
337
        if (auto map = dynamic_cast<structured_control_flow::Map*>(loop)) {
×
UNCOV
338
            new_loop = &builder.add_map(
×
UNCOV
339
                *current_parent,
×
340
                loop->indvar(),
×
341
                loop->condition(),
×
UNCOV
342
                loop->init(),
×
UNCOV
343
                loop->update(),
×
344
                map->schedule_type(),
×
345
                {},
×
346
                loop->debug_info()
×
UNCOV
347
            );
×
348
        } else {
×
349
            new_loop = &builder.add_for(
×
350
                *current_parent, loop->indvar(), loop->condition(), loop->init(), loop->update(), {}, loop->debug_info()
×
351
            );
×
352
        }
×
353
        current_parent = &new_loop->root();
×
354
    }
×
355

356
    // Deep copy the innermost loop's body into the remainder innermost loop
357
    deepcopy::StructuredSDFGDeepCopy remainder_copier(builder, *current_parent, innermost->root());
×
358
    remainder_copier.insert();
×
359

360
    // Remove the original loop
361
    builder.remove_child(*parent, parent->index(loop_));
×
362

363
    analysis_manager.invalidate_all();
×
364
};
×
365

366
void LoopPeeling::to_json(nlohmann::json& j) const {
×
367
    j["transformation_type"] = this->name();
×
368
    j["parameters"] = nlohmann::json::object();
×
369

370
    serializer::JSONSerializer ser_flat(false);
×
371
    j["subgraph"] = nlohmann::json::object();
×
372
    j["subgraph"]["0"] = nlohmann::json::object();
×
373
    ser_flat.serialize_node(j["subgraph"]["0"], loop_);
×
374
};
×
375

376
LoopPeeling LoopPeeling::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
×
377
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
×
378
    auto element = builder.find_element_by_id(loop_id);
×
UNCOV
379
    if (element == nullptr) {
×
UNCOV
380
        throw InvalidTransformationDescriptionException("Element with ID " + std::to_string(loop_id) + " not found.");
×
381
    }
×
382
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
×
UNCOV
383
    if (loop == nullptr) {
×
UNCOV
384
        throw InvalidTransformationDescriptionException(
×
385
            "Element with ID " + std::to_string(loop_id) + " is not a StructuredLoop."
×
UNCOV
386
        );
×
387
    }
×
388

UNCOV
389
    return LoopPeeling(*loop);
×
390
};
×
391

392
} // namespace transformations
393
} // 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