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

daisytuner / docc / 25274982887

03 May 2026 09:03AM UTC coverage: 64.204%. First build
25274982887

Pull #697

github

web-flow
Merge 944e077f0 into ce75eea52
Pull Request #697: adds loop peeling

0 of 221 new or added lines in 2 files covered. (0.0%)

31345 of 48821 relevant lines covered (64.2%)

1385.3 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/analysis/scope_analysis.h"
4
#include "sdfg/builder/structured_sdfg_builder.h"
5
#include "sdfg/deepcopy/structured_sdfg_deep_copy.h"
6
#include "sdfg/structured_control_flow/for.h"
7
#include "sdfg/structured_control_flow/map.h"
8
#include "sdfg/structured_control_flow/sequence.h"
9
#include "sdfg/symbolic/symbolic.h"
10

11
#include <symengine/logic.h>
12

13
namespace sdfg {
14
namespace transformations {
15

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

NEW
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.
NEW
22
static symbolic::Expression extract_upper_bound(const symbolic::Condition& cond, const symbolic::Symbol& indvar) {
×
NEW
23
    if (!SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
×
NEW
24
        return SymEngine::null;
×
NEW
25
    }
×
NEW
26
    auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
×
NEW
27
    auto lhs = lt->get_arg1();
×
NEW
28
    auto rhs = lt->get_arg2();
×
NEW
29
    if (symbolic::eq(lhs, indvar)) {
×
NEW
30
        return rhs;
×
NEW
31
    }
×
NEW
32
    return SymEngine::null;
×
NEW
33
}
×
34

35
/// Determine if `bound - init` simplifies to a positive integer constant.
NEW
36
static bool is_constant_trip_bound(const symbolic::Expression& bound, const symbolic::Expression& init) {
×
NEW
37
    auto diff = symbolic::sub(bound, init);
×
NEW
38
    if (SymEngine::is_a<SymEngine::Integer>(*diff)) {
×
NEW
39
        auto val = SymEngine::rcp_static_cast<const SymEngine::Integer>(diff);
×
NEW
40
        return val->as_int() > 0;
×
NEW
41
    }
×
NEW
42
    return false;
×
NEW
43
}
×
44

45
/// Check if a loop has a compound condition with at least one canonical bound.
NEW
46
static bool loop_is_peelable(structured_control_flow::StructuredLoop& loop) {
×
NEW
47
    auto cond = loop.condition();
×
NEW
48
    if (!SymEngine::is_a<SymEngine::And>(*cond)) {
×
NEW
49
        return false;
×
NEW
50
    }
×
NEW
51
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
NEW
52
    auto& conjuncts = and_cond->get_container();
×
NEW
53
    if (conjuncts.size() < 2) {
×
NEW
54
        return false;
×
NEW
55
    }
×
56

NEW
57
    auto indvar = loop.indvar();
×
NEW
58
    auto init = loop.init();
×
NEW
59
    bool has_canonical = false;
×
NEW
60
    bool has_dynamic = false;
×
61

NEW
62
    for (auto& conjunct : conjuncts) {
×
NEW
63
        auto bound = extract_upper_bound(conjunct, indvar);
×
NEW
64
        if (bound == SymEngine::null) {
×
NEW
65
            return false;
×
NEW
66
        }
×
NEW
67
        if (is_constant_trip_bound(bound, init)) {
×
NEW
68
            has_canonical = true;
×
NEW
69
        } else {
×
NEW
70
            has_dynamic = true;
×
NEW
71
        }
×
NEW
72
    }
×
NEW
73
    return has_canonical && has_dynamic;
×
NEW
74
}
×
75

76
/// For a peelable loop, extract the canonical bound (tightest constant-trip bound).
NEW
77
static symbolic::Expression find_canonical_bound(structured_control_flow::StructuredLoop& loop) {
×
NEW
78
    auto cond = loop.condition();
×
NEW
79
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
NEW
80
    auto& conjuncts = and_cond->get_container();
×
NEW
81
    auto indvar = loop.indvar();
×
NEW
82
    auto init = loop.init();
×
83

NEW
84
    symbolic::Expression canonical = SymEngine::null;
×
NEW
85
    for (auto& conjunct : conjuncts) {
×
NEW
86
        auto bound = extract_upper_bound(conjunct, indvar);
×
NEW
87
        if (bound == SymEngine::null) continue;
×
NEW
88
        if (!is_constant_trip_bound(bound, init)) continue;
×
89

NEW
90
        if (canonical == SymEngine::null) {
×
NEW
91
            canonical = bound;
×
NEW
92
        } else {
×
NEW
93
            auto existing_trip = SymEngine::rcp_static_cast<const SymEngine::Integer>(symbolic::sub(canonical, init));
×
NEW
94
            auto new_trip = SymEngine::rcp_static_cast<const SymEngine::Integer>(symbolic::sub(bound, init));
×
NEW
95
            if (new_trip->as_int() < existing_trip->as_int()) {
×
NEW
96
                canonical = bound;
×
NEW
97
            }
×
NEW
98
        }
×
NEW
99
    }
×
NEW
100
    return canonical;
×
NEW
101
}
×
102

103
/// Build the peeling condition for a single loop: canonical_bound <= each dynamic bound.
104
static symbolic::Condition build_loop_peeling_condition(
105
    structured_control_flow::StructuredLoop& loop, const symbolic::Expression& canonical_bound
NEW
106
) {
×
NEW
107
    auto cond = loop.condition();
×
NEW
108
    auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
NEW
109
    auto& conjuncts = and_cond->get_container();
×
NEW
110
    auto indvar = loop.indvar();
×
NEW
111
    auto init = loop.init();
×
112

NEW
113
    symbolic::Condition result = SymEngine::boolTrue;
×
NEW
114
    for (auto& conjunct : conjuncts) {
×
NEW
115
        auto bound = extract_upper_bound(conjunct, indvar);
×
NEW
116
        if (bound == SymEngine::null) continue;
×
NEW
117
        if (is_constant_trip_bound(bound, init) && symbolic::eq(bound, canonical_bound)) continue;
×
118
        // canonical_bound <= this dynamic/looser bound
NEW
119
        result = symbolic::And(result, symbolic::Le(canonical_bound, bound));
×
NEW
120
    }
×
NEW
121
    return result;
×
NEW
122
}
×
123

124
/// Collect the perfectly nested chain of peelable loops starting from `loop`.
125
/// A chain continues as long as the loop body has exactly one child which is
126
/// another peelable structured loop.
127
static std::vector<structured_control_flow::StructuredLoop*> collect_peelable_nest(structured_control_flow::StructuredLoop&
NEW
128
                                                                                       loop) {
×
NEW
129
    std::vector<structured_control_flow::StructuredLoop*> nest;
×
NEW
130
    nest.push_back(&loop);
×
131

NEW
132
    auto* current = &loop;
×
NEW
133
    while (true) {
×
NEW
134
        auto& body = current->root();
×
NEW
135
        if (body.size() != 1) break;
×
NEW
136
        auto& child = body.at(0).first;
×
NEW
137
        auto* inner_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(&child);
×
NEW
138
        if (!inner_loop) break;
×
NEW
139
        if (!loop_is_peelable(*inner_loop)) break;
×
NEW
140
        nest.push_back(inner_loop);
×
NEW
141
        current = inner_loop;
×
NEW
142
    }
×
NEW
143
    return nest;
×
NEW
144
}
×
145

NEW
146
bool LoopPeeling::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
×
NEW
147
    return loop_is_peelable(loop_);
×
NEW
148
};
×
149

NEW
150
void LoopPeeling::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
×
NEW
151
    auto& sdfg = builder.subject();
×
152

153
    // Collect perfectly nested chain of peelable loops
NEW
154
    auto nest = collect_peelable_nest(loop_);
×
155

156
    // For each loop in the nest, find its canonical bound and build peeling condition
NEW
157
    struct LoopPeelInfo {
×
NEW
158
        structured_control_flow::StructuredLoop* loop;
×
NEW
159
        symbolic::Expression canonical_bound;
×
NEW
160
        symbolic::Condition peeling_condition;
×
NEW
161
    };
×
NEW
162
    std::vector<LoopPeelInfo> peel_infos;
×
163

NEW
164
    symbolic::Condition combined_peeling_condition = SymEngine::boolTrue;
×
NEW
165
    for (auto* loop : nest) {
×
NEW
166
        auto canonical = find_canonical_bound(*loop);
×
NEW
167
        auto peel_cond = build_loop_peeling_condition(*loop, canonical);
×
NEW
168
        combined_peeling_condition = symbolic::And(combined_peeling_condition, peel_cond);
×
NEW
169
        peel_infos.push_back({loop, canonical, peel_cond});
×
NEW
170
    }
×
171

172
    // Get parent scope of the outermost loop
NEW
173
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
×
NEW
174
    auto parent = static_cast<structured_control_flow::Sequence*>(scope_analysis.parent_scope(&loop_));
×
175

176
    // Create IfElse before the outermost loop
NEW
177
    auto& if_else = builder.add_if_else_before(*parent, loop_, {}, loop_.debug_info());
×
178

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

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

NEW
187
    for (size_t i = 0; i < peel_infos.size(); i++) {
×
NEW
188
        auto& info = peel_infos[i];
×
NEW
189
        auto* loop = info.loop;
×
NEW
190
        auto indvar = loop->indvar();
×
NEW
191
        auto init = loop->init();
×
192

193
        // Compute constant trip count: canonical_bound - init
NEW
194
        auto trip_count = symbolic::sub(info.canonical_bound, init);
×
195

196
        // New loop: indvar goes from 0 to trip_count
NEW
197
        auto zero_condition = symbolic::Lt(indvar, trip_count);
×
NEW
198
        auto zero_init = symbolic::integer(0);
×
199

NEW
200
        structured_control_flow::StructuredLoop* new_loop = nullptr;
×
NEW
201
        if (auto map = dynamic_cast<structured_control_flow::Map*>(loop)) {
×
NEW
202
            new_loop = &builder.add_map(
×
NEW
203
                *current_parent,
×
NEW
204
                indvar,
×
NEW
205
                zero_condition,
×
NEW
206
                zero_init,
×
NEW
207
                loop->update(),
×
NEW
208
                map->schedule_type(),
×
NEW
209
                {},
×
NEW
210
                loop->debug_info()
×
NEW
211
            );
×
NEW
212
        } else {
×
NEW
213
            new_loop =
×
NEW
214
                &builder
×
NEW
215
                     .add_for(*current_parent, indvar, zero_condition, zero_init, loop->update(), {}, loop->debug_info());
×
NEW
216
        }
×
217

218
        // Record substitution: in the body, original indvar usage = new_indvar + init
NEW
219
        substitutions.push_back({indvar, init});
×
NEW
220
        current_parent = &new_loop->root();
×
NEW
221
    }
×
222

223
    // Deep copy the innermost loop's body into the new innermost loop
NEW
224
    auto* innermost = nest.back();
×
NEW
225
    deepcopy::StructuredSDFGDeepCopy main_copier(builder, *current_parent, innermost->root());
×
NEW
226
    main_copier.insert();
×
227

228
    // Apply shift substitutions in the copied body:
229
    // Replace indvar with (indvar + original_init) so body accesses use correct offsets.
230
    // Must apply outermost first (since inner body may reference outer indvars).
NEW
231
    for (auto& [indvar, init] : substitutions) {
×
NEW
232
        if (symbolic::eq(init, symbolic::zero())) continue;
×
NEW
233
        auto shifted_expr = symbolic::add(indvar, init);
×
NEW
234
        current_parent->replace(indvar, shifted_expr);
×
NEW
235
    }
×
236

237
    // === Else branch: original compound bounds (remainder) ===
NEW
238
    auto else_condition = symbolic::Not(combined_peeling_condition);
×
NEW
239
    auto& else_branch = builder.add_case(if_else, else_condition);
×
240

241
    // Build the nest of loops with original conditions in the else branch
NEW
242
    current_parent = &else_branch;
×
NEW
243
    for (size_t i = 0; i < peel_infos.size(); i++) {
×
NEW
244
        auto* loop = peel_infos[i].loop;
×
245

NEW
246
        structured_control_flow::StructuredLoop* new_loop = nullptr;
×
NEW
247
        if (auto map = dynamic_cast<structured_control_flow::Map*>(loop)) {
×
NEW
248
            new_loop = &builder.add_map(
×
NEW
249
                *current_parent,
×
NEW
250
                loop->indvar(),
×
NEW
251
                loop->condition(),
×
NEW
252
                loop->init(),
×
NEW
253
                loop->update(),
×
NEW
254
                map->schedule_type(),
×
NEW
255
                {},
×
NEW
256
                loop->debug_info()
×
NEW
257
            );
×
NEW
258
        } else {
×
NEW
259
            new_loop = &builder.add_for(
×
NEW
260
                *current_parent, loop->indvar(), loop->condition(), loop->init(), loop->update(), {}, loop->debug_info()
×
NEW
261
            );
×
NEW
262
        }
×
NEW
263
        current_parent = &new_loop->root();
×
NEW
264
    }
×
265

266
    // Deep copy the innermost loop's body into the remainder innermost loop
NEW
267
    deepcopy::StructuredSDFGDeepCopy remainder_copier(builder, *current_parent, innermost->root());
×
NEW
268
    remainder_copier.insert();
×
269

270
    // Remove the original loop
NEW
271
    builder.remove_child(*parent, parent->index(loop_));
×
272

NEW
273
    analysis_manager.invalidate_all();
×
NEW
274
};
×
275

NEW
276
void LoopPeeling::to_json(nlohmann::json& j) const {
×
NEW
277
    std::string loop_type;
×
NEW
278
    if (dynamic_cast<structured_control_flow::For*>(&loop_)) {
×
NEW
279
        loop_type = "for";
×
NEW
280
    } else if (dynamic_cast<structured_control_flow::Map*>(&loop_)) {
×
NEW
281
        loop_type = "map";
×
NEW
282
    } else {
×
NEW
283
        throw std::runtime_error("Unsupported loop type for serialization of loop: " + loop_.indvar()->get_name());
×
NEW
284
    }
×
285

NEW
286
    j["transformation_type"] = this->name();
×
NEW
287
    j["subgraph"] = {{"0", {{"element_id", this->loop_.element_id()}, {"type", loop_type}}}};
×
NEW
288
};
×
289

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

NEW
303
    return LoopPeeling(*loop);
×
NEW
304
};
×
305

306
} // namespace transformations
307
} // 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