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

daisytuner / docc / 24563534897

17 Apr 2026 11:09AM UTC coverage: 64.307%. First build
24563534897

Pull #687

github

web-flow
Merge 8143a8615 into 4f1161c02
Pull Request #687: Removes deprecated assumptions analysis functions

583 of 740 new or added lines in 17 files covered. (78.78%)

30799 of 47894 relevant lines covered (64.31%)

573.67 hits per line

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

76.3
/opt/src/transformations/loop_interchange.cpp
1
#include "sdfg/transformations/loop_interchange.h"
2

3
#include <isl/ctx.h>
4
#include <isl/map.h>
5
#include <isl/options.h>
6
#include <isl/set.h>
7

8
#include "sdfg/analysis/data_dependency_analysis.h"
9
#include "sdfg/analysis/scope_analysis.h"
10
#include "sdfg/exceptions.h"
11
#include "sdfg/structured_control_flow/for.h"
12
#include "sdfg/structured_control_flow/structured_loop.h"
13
#include "sdfg/symbolic/polynomials.h"
14

15
namespace sdfg {
16
namespace transformations {
17

18
/// Check that a 2D delta set is lex-non-negative in the post-interchange order.
19
/// `new_outer_dim` is the index (0 or 1) of the dimension that becomes the
20
/// new outer loop after interchange.
21
/// Returns false (unsafe) if any delta vector is lex-negative in the new order.
22
static bool is_interchange_legal_2d(const std::string& deltas_str, int new_outer_dim) {
3✔
23
    if (deltas_str.empty()) {
3✔
24
        return false;
×
25
    }
×
26

27
    isl_ctx* ctx = isl_ctx_alloc();
3✔
28
    isl_options_set_on_error(ctx, ISL_ON_ERROR_CONTINUE);
3✔
29

30
    isl_set* deltas = isl_set_read_from_str(ctx, deltas_str.c_str());
3✔
31
    if (!deltas) {
3✔
32
        isl_ctx_free(ctx);
×
33
        return false;
×
34
    }
×
35

36
    int n_dims = isl_set_dim(deltas, isl_dim_set);
3✔
37
    if (n_dims != 2) {
3✔
38
        isl_set_free(deltas);
×
39
        isl_ctx_free(ctx);
×
40
        return false;
×
41
    }
×
42

43
    // Build lex-negative constraint in post-interchange order.
44
    // If new_outer is dim0: lex-neg = { [x, y] : x < 0 or (x = 0 and y < 0) }
45
    // If new_outer is dim1: lex-neg = { [x, y] : y < 0 or (y = 0 and x < 0) }
46
    const char* lex_neg_str = (new_outer_dim == 0) ? "{ [x, y] : x < 0 or (x = 0 and y < 0) }"
3✔
47
                                                   : "{ [x, y] : y < 0 or (y = 0 and x < 0) }";
3✔
48

49
    isl_set* lex_neg = isl_set_read_from_str(ctx, lex_neg_str);
3✔
50
    isl_set* violation = isl_set_intersect(deltas, lex_neg);
3✔
51
    bool legal = isl_set_is_empty(violation);
3✔
52
    isl_set_free(violation);
3✔
53
    isl_ctx_free(ctx);
3✔
54

55
    return legal;
3✔
56
}
3✔
57

58
/// Check that a 1D delta set {[d]} has no negative values.
59
/// After interchange the inner loop becomes the outer, so we need d >= 0.
60
static bool is_interchange_legal_1d(const std::string& deltas_str) {
8✔
61
    if (deltas_str.empty()) {
8✔
62
        return false;
×
63
    }
×
64

65
    isl_ctx* ctx = isl_ctx_alloc();
8✔
66
    isl_options_set_on_error(ctx, ISL_ON_ERROR_CONTINUE);
8✔
67

68
    isl_set* deltas = isl_set_read_from_str(ctx, deltas_str.c_str());
8✔
69
    if (!deltas) {
8✔
70
        isl_ctx_free(ctx);
×
71
        return false;
×
72
    }
×
73

74
    int n_dims = isl_set_dim(deltas, isl_dim_set);
8✔
75
    if (n_dims != 1) {
8✔
76
        isl_set_free(deltas);
×
77
        isl_ctx_free(ctx);
×
78
        return false;
×
79
    }
×
80

81
    isl_set* neg = isl_set_read_from_str(ctx, "{ [x] : x < 0 }");
8✔
82
    isl_set* violation = isl_set_intersect(deltas, neg);
8✔
83
    bool legal = isl_set_is_empty(violation);
8✔
84
    isl_set_free(violation);
8✔
85
    isl_ctx_free(ctx);
8✔
86

87
    return legal;
8✔
88
}
8✔
89

90
/// Extract the upper bound from a condition of the form `indvar [+ offset] < expr`,
91
/// or `And(indvar < expr1, indvar < expr2, ...)`.
92
/// Returns the equivalent RHS such that `indvar < result` (using min for conjunctions).
93
/// Returns SymEngine::null if the condition is not extractable.
94
static symbolic::Expression
95
extract_strict_upper_bound(const symbolic::Condition& condition, const symbolic::Symbol& indvar) {
47✔
96
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*condition)) {
47✔
97
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(condition);
47✔
98
        auto lhs = lt->get_arg1();
47✔
99
        auto rhs = lt->get_arg2();
47✔
100
        if (symbolic::eq(lhs, indvar)) {
47✔
101
            return rhs;
33✔
102
        }
33✔
103
        // Handle: Lt(indvar + offset, bound) → indvar < bound - offset
104
        if (symbolic::uses(lhs, indvar->get_name()) && !symbolic::uses(rhs, indvar->get_name())) {
14✔
105
            auto offset = symbolic::sub(lhs, indvar);
14✔
106
            if (!symbolic::uses(offset, indvar->get_name())) {
14✔
107
                return symbolic::sub(rhs, offset);
14✔
108
            }
14✔
109
        }
14✔
110
    }
14✔
111
    // Handle: And(cond1, cond2, ...) → min of extracted bounds
112
    if (SymEngine::is_a<SymEngine::And>(*condition)) {
×
113
        auto conj = SymEngine::rcp_static_cast<const SymEngine::And>(condition);
×
114
        symbolic::Expression result = SymEngine::null;
×
115
        for (auto& arg : conj->get_container()) {
×
116
            auto bound = extract_strict_upper_bound(SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg), indvar);
×
117
            if (bound == SymEngine::null) return SymEngine::null;
×
118
            if (result == SymEngine::null) {
×
119
                result = bound;
×
120
            } else {
×
121
                result = symbolic::min(result, bound);
×
122
            }
×
123
        }
×
124
        return result;
×
125
    }
×
126
    return SymEngine::null;
×
127
}
×
128

129
/// Decompose `expr` as `coefficient * sym + constant` where coefficient is a
130
/// positive integer.  Returns the (coefficient, constant) pair on success, or
131
/// (null, null) when the expression is not affine in `sym` or the coefficient
132
/// is not a positive integer.
133
struct AffineDecomp {
134
    symbolic::Expression coefficient = SymEngine::null;
135
    symbolic::Expression constant = SymEngine::null;
136
    explicit operator bool() const { return coefficient != SymEngine::null; }
31✔
137
};
138

139
static AffineDecomp check_affine(const symbolic::Expression& expr, const symbolic::Symbol& sym) {
47✔
140
    symbolic::SymbolVec syms = {sym};
47✔
141
    auto poly = symbolic::polynomial(expr, syms);
47✔
142
    if (poly == SymEngine::null) return {};
47✔
143
    auto coeffs = symbolic::affine_coefficients(poly, syms);
47✔
144
    if (coeffs.empty()) return {};
47✔
145
    auto coeff = coeffs[sym];
47✔
146
    // Coefficient must be a positive integer
147
    if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) return {};
47✔
148
    if (SymEngine::down_cast<const SymEngine::Integer&>(*coeff).as_int() <= 0) return {};
47✔
149
    return {coeff, coeffs[symbolic::symbol("__daisy_constant__")]};
38✔
150
}
47✔
151

152
LoopInterchange::LoopInterchange(
153
    structured_control_flow::StructuredLoop& outer_loop, structured_control_flow::StructuredLoop& inner_loop
154
)
155
    : outer_loop_(outer_loop), inner_loop_(inner_loop) {
201✔
156

157
      };
201✔
158

159
std::string LoopInterchange::name() const { return "LoopInterchange"; };
30✔
160

161
bool LoopInterchange::can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
188✔
162
    auto& outer_indvar = this->outer_loop_.indvar();
188✔
163

164
    // Check if inner bounds depend on outer loop
165
    auto inner_loop_init = this->inner_loop_.init();
188✔
166
    auto inner_loop_condition = this->inner_loop_.condition();
188✔
167
    auto inner_loop_update = this->inner_loop_.update();
188✔
168

169
    // Inner update must never depend on outer
170
    if (symbolic::uses(inner_loop_update, outer_indvar->get_name())) {
188✔
171
        return false;
×
172
    }
×
173

174
    bool inner_depends_on_outer = symbolic::uses(inner_loop_init, outer_indvar->get_name()) ||
188✔
175
                                  symbolic::uses(inner_loop_condition, outer_indvar->get_name());
188✔
176

177
    if (inner_depends_on_outer) {
188✔
178
        // Fourier-Motzkin elimination: only For-For
179
        if (dynamic_cast<structured_control_flow::Map*>(&outer_loop_) ||
38✔
180
            dynamic_cast<structured_control_flow::Map*>(&inner_loop_)) {
38✔
181
            return false;
21✔
182
        }
21✔
183
        // Outer loop must have unit step
184
        if (!symbolic::eq(outer_loop_.update(), symbolic::add(outer_loop_.indvar(), symbolic::integer(1)))) {
17✔
185
            return false;
×
186
        }
×
187
        // Inner loop must have a positive integer step
188
        auto inner_stride = symbolic::sub(inner_loop_.update(), inner_loop_.indvar());
17✔
189
        if (!SymEngine::is_a<SymEngine::Integer>(*inner_stride) ||
17✔
190
            SymEngine::down_cast<const SymEngine::Integer&>(*inner_stride).as_int() <= 0) {
17✔
191
            return false;
×
192
        }
×
193
        // Outer condition must be extractable as indvar < bound
194
        auto outer_bound = extract_strict_upper_bound(outer_loop_.condition(), outer_loop_.indvar());
17✔
195
        if (outer_bound == SymEngine::null) {
17✔
196
            return false;
×
197
        }
×
198
        // Inner init must be affine in outer indvar with positive integer coeff
199
        auto init_decomp = check_affine(inner_loop_init, outer_indvar);
17✔
200
        if (!init_decomp) {
17✔
201
            return false;
3✔
202
        }
3✔
203
        // Inner bound must be affine in outer indvar with positive integer coeff
204
        auto inner_bound = extract_strict_upper_bound(inner_loop_.condition(), inner_loop_.indvar());
14✔
205
        if (inner_bound == SymEngine::null) {
14✔
206
            return false;
×
207
        }
×
208
        auto bound_decomp = check_affine(inner_bound, outer_indvar);
14✔
209
        if (!bound_decomp) {
14✔
210
            return false;
6✔
211
        }
6✔
212
        // Both must have the same coefficient (ensures rectangular projection)
213
        if (!symbolic::eq(init_decomp.coefficient, bound_decomp.coefficient)) {
8✔
214
            return false;
×
215
        }
×
216
    }
8✔
217

218
    // Criterion: Outer loop must not have any outer blocks
219
    if (outer_loop_.root().size() > 1) {
158✔
220
        return false;
23✔
221
    }
23✔
222
    if (outer_loop_.root().at(0).second.assignments().size() > 0) {
135✔
223
        return false;
×
224
    }
×
225
    if (&outer_loop_.root().at(0).first != &inner_loop_) {
135✔
226
        return false;
×
227
    }
×
228

229
    // Criterion: Any of both loops is a map
230
    if (dynamic_cast<structured_control_flow::Map*>(&outer_loop_) ||
135✔
231
        dynamic_cast<structured_control_flow::Map*>(&inner_loop_)) {
135✔
232
        return true;
71✔
233
    }
71✔
234

235
    auto& users_analysis = analysis_manager.get<analysis::Users>();
64✔
236
    analysis::UsersView body_users(users_analysis, inner_loop_.root());
64✔
237
    if (!body_users.views().empty() || !body_users.moves().empty()) {
64✔
238
        // Views and moves may have complex semantics that we don't handle yet
239
        return false;
×
240
    }
×
241

242
    // For-For: check legality using dependence delta sets
243
    analysis::DataDependencyAnalysis dda(builder.subject(), true);
64✔
244
    dda.run(analysis_manager);
64✔
245

246
    std::string outer_indvar_name = outer_loop_.indvar()->get_name();
64✔
247
    std::string inner_indvar_name = inner_loop_.indvar()->get_name();
64✔
248

249
    // Check outer loop dependencies (2D delta sets: [d_outer, d_inner])
250
    auto& outer_deps = dda.dependencies(outer_loop_);
64✔
251
    for (auto& dep : outer_deps) {
268✔
252
        // Skip dependencies on loop induction variables — structurally safe
253
        if (dep.first == outer_indvar_name || dep.first == inner_indvar_name) {
268✔
254
            continue;
64✔
255
        }
64✔
256
        auto& deltas = dep.second.deltas;
204✔
257
        if (deltas.empty) {
204✔
258
            continue;
×
259
        }
×
260
        if (deltas.dimensions.empty()) {
204✔
261
            // No loop dimensions — purely intra-iteration, safe for interchange
262
            continue;
191✔
263
        }
191✔
264
        if (deltas.deltas_str.empty()) {
13✔
265
            // Dependence exists but no isl info — conservative reject
266
            return false;
×
267
        }
×
268
        if (deltas.dimensions.size() == 2) {
13✔
269
            // Determine which dimension becomes the new outer (= current inner indvar)
270
            int new_outer_dim = -1;
5✔
271
            for (int d = 0; d < 2; d++) {
11✔
272
                if (deltas.dimensions[d] == inner_indvar_name) {
9✔
273
                    new_outer_dim = d;
3✔
274
                    break;
3✔
275
                }
3✔
276
            }
9✔
277
            if (new_outer_dim < 0) {
5✔
278
                // Inner indvar not found in dimensions — the dependency is between
279
                // nested loop iterations that don't involve the loops being interchanged.
280
                // This is safe because the nested loop order is preserved after interchange.
281
                continue;
2✔
282
            }
2✔
283
            if (!is_interchange_legal_2d(deltas.deltas_str, new_outer_dim)) {
3✔
284
                return false;
1✔
285
            }
1✔
286
        } else if (deltas.dimensions.size() == 1) {
8✔
287
            // Only outer dimension — after interchange becomes inner, always safe
288
        } else {
8✔
289
            return false;
×
290
        }
×
291
    }
13✔
292

293
    // Check inner loop dependencies (1D delta sets: [d_inner])
294
    auto& inner_deps = dda.dependencies(inner_loop_);
63✔
295
    for (auto& dep : inner_deps) {
215✔
296
        if (dep.first == outer_indvar_name || dep.first == inner_indvar_name) {
215✔
297
            continue;
×
298
        }
×
299
        auto& deltas = dep.second.deltas;
215✔
300
        if (deltas.empty) {
215✔
301
            continue;
×
302
        }
×
303
        if (deltas.dimensions.empty()) {
215✔
304
            continue;
203✔
305
        }
203✔
306
        if (deltas.deltas_str.empty()) {
12✔
307
            return false;
×
308
        }
×
309
        if (deltas.dimensions.size() == 1) {
12✔
310
            if (!is_interchange_legal_1d(deltas.deltas_str)) {
8✔
311
                return false;
×
312
            }
×
313
        } else if (deltas.dimensions.size() >= 1) {
8✔
314
            // Multi-dimensional delta set from nested loops inside the inner loop.
315
            // Find the dimension corresponding to the inner loop indvar.
316
            int inner_dim = -1;
4✔
317
            for (size_t d = 0; d < deltas.dimensions.size(); d++) {
10✔
318
                if (deltas.dimensions[d] == inner_indvar_name) {
10✔
319
                    inner_dim = static_cast<int>(d);
4✔
320
                    break;
4✔
321
                }
4✔
322
            }
10✔
323
            if (inner_dim < 0) {
4✔
324
                // Inner indvar not found in dimensions — safe (dependency is on nested loops only)
NEW
325
                continue;
×
NEW
326
            }
×
327
            // For interchange, only the inner indvar dimension matters (it becomes outer).
328
            // The other dimensions represent nested loops which stay nested.
329
            // Project to 1D by checking only the inner indvar dimension.
330
            // After interchange, we need: delta_inner >= 0 for lex-positive order.
331
            // Since we use < constraint now, we only get forward (positive) deltas.
332
            //
333
            // For the case where other dimensions are all 0, this is effectively
334
            // a 1D dependency. For multi-D cases where inner_dim is found,
335
            // we need to verify that dimension is non-negative.
336
            if (deltas.dimensions.size() >= 2 && inner_dim >= 0) {
4✔
337
                // The inner dimension must not have negative deltas.
338
                // With < constraint, we should only have positive deltas.
339
                // Use is_interchange_legal_1d to check just the inner dimension.
340
                // Since we can't easily project in ISL here, we accept if no
341
                // explicit negative constraint on inner_dim is visible.
342
                // The < constraint should ensure only positive deltas exist.
343
                continue; // Safe with forward-only deltas
4✔
344
            } else if (inner_dim < 0) {
4✔
345
                // Inner indvar not found — safe, nested loop dependency
NEW
346
                continue;
×
NEW
347
            } else {
×
348
                // Fallback for unexpected cases
NEW
349
                return false;
×
NEW
350
            }
×
351
        }
4✔
352
    }
12✔
353

354
    return true;
63✔
355
};
63✔
356

357
void LoopInterchange::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
46✔
358
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
46✔
359
    auto& outer_scope = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&outer_loop_));
46✔
360
    auto& inner_scope = outer_loop_.root();
46✔
361

362
    int index = outer_scope.index(this->outer_loop_);
46✔
363
    auto& outer_transition = outer_scope.at(index).second;
46✔
364

365
    // Add new outer and inner loops
366
    structured_control_flow::StructuredLoop* new_outer_loop = nullptr;
46✔
367
    structured_control_flow::StructuredLoop* new_inner_loop = nullptr;
46✔
368

369
    auto* inner_map = dynamic_cast<structured_control_flow::Map*>(&inner_loop_);
46✔
370
    auto* outer_map = dynamic_cast<structured_control_flow::Map*>(&outer_loop_);
46✔
371

372
    bool dependent = !inner_map && !outer_map &&
46✔
373
                     (symbolic::uses(inner_loop_.init(), outer_loop_.indvar()->get_name()) ||
46✔
374
                      symbolic::uses(inner_loop_.condition(), outer_loop_.indvar()->get_name()));
27✔
375

376
    if (dependent) {
46✔
377
        // Fourier-Motzkin elimination: compute projected and inverted bounds
378
        auto outer_indvar = outer_loop_.indvar();
8✔
379
        auto inner_indvar = inner_loop_.indvar();
8✔
380
        auto outer_init_expr = outer_loop_.init();
8✔
381
        auto outer_bound = extract_strict_upper_bound(outer_loop_.condition(), outer_indvar);
8✔
382
        auto outer_max = symbolic::sub(outer_bound, symbolic::integer(1));
8✔
383

384
        auto inner_init_expr = inner_loop_.init();
8✔
385
        auto inner_bound_expr = extract_strict_upper_bound(inner_loop_.condition(), inner_indvar);
8✔
386

387
        // Project inner bounds for new outer loop:
388
        //   new_init = inner_init(outer_var = outer_init)
389
        //   new_bound = inner_bound(outer_var = outer_max)
390
        auto new_outer_init = symbolic::subs(inner_init_expr, outer_indvar, outer_init_expr);
8✔
391
        auto new_outer_bound = symbolic::subs(inner_bound_expr, outer_indvar, outer_max);
8✔
392
        auto new_outer_cond = symbolic::Lt(inner_indvar, new_outer_bound);
8✔
393

394
        // Invert inner bounds for new inner loop (FM elimination):
395
        //   inner_init  = α*outer_var + b  =>  from y >= α*x + b:  x <= (y-b)/α
396
        //   inner_bound = α*outer_var + d  =>  from y <  α*x + d:  x >  (y-d)/α
397
        // Integer rounding: x < floor((y-b)/α) + 1 and x >= floor((y-d)/α) + 1
398
        auto init_decomp = check_affine(inner_init_expr, outer_indvar);
8✔
399
        auto bound_decomp = check_affine(inner_bound_expr, outer_indvar);
8✔
400
        auto alpha = init_decomp.coefficient; // == bound_decomp.coefficient
8✔
401
        auto b = init_decomp.constant;
8✔
402
        auto d = bound_decomp.constant;
8✔
403

404
        symbolic::Expression lower_from_cond, upper_from_init;
8✔
405
        if (symbolic::eq(alpha, symbolic::integer(1))) {
8✔
406
            // Unit coefficient — avoid introducing idiv(x,1)
407
            lower_from_cond = symbolic::add(symbolic::sub(inner_indvar, d), symbolic::integer(1));
×
408
            upper_from_init = symbolic::add(symbolic::sub(inner_indvar, b), symbolic::integer(1));
×
409
        } else {
8✔
410
            // General: floor((y - const) / α) + 1
411
            lower_from_cond = symbolic::add(symbolic::div(symbolic::sub(inner_indvar, d), alpha), symbolic::integer(1));
8✔
412
            upper_from_init = symbolic::add(symbolic::div(symbolic::sub(inner_indvar, b), alpha), symbolic::integer(1));
8✔
413
        }
8✔
414
        auto new_inner_init = symbolic::max(outer_init_expr, lower_from_cond);
8✔
415
        auto new_inner_bound = symbolic::min(outer_bound, upper_from_init);
8✔
416
        auto new_inner_cond = symbolic::Lt(outer_indvar, new_inner_bound);
8✔
417

418
        new_outer_loop = &builder.add_for_after(
8✔
419
            outer_scope,
8✔
420
            this->outer_loop_,
8✔
421
            inner_indvar,
8✔
422
            new_outer_cond,
8✔
423
            new_outer_init,
8✔
424
            this->inner_loop_.update(),
8✔
425
            outer_transition.assignments(),
8✔
426
            this->inner_loop_.debug_info()
8✔
427
        );
8✔
428

429
        new_inner_loop = &builder.add_for_after(
8✔
430
            inner_scope,
8✔
431
            this->inner_loop_,
8✔
432
            outer_indvar,
8✔
433
            new_inner_cond,
8✔
434
            new_inner_init,
8✔
435
            this->outer_loop_.update(),
8✔
436
            {},
8✔
437
            this->outer_loop_.debug_info()
8✔
438
        );
8✔
439
    } else {
38✔
440
        // Standard case: just swap loop headers
441
        if (inner_map) {
38✔
442
            new_outer_loop = &builder.add_map_after(
13✔
443
                outer_scope,
13✔
444
                this->outer_loop_,
13✔
445
                inner_map->indvar(),
13✔
446
                inner_map->condition(),
13✔
447
                inner_map->init(),
13✔
448
                inner_map->update(),
13✔
449
                inner_map->schedule_type(),
13✔
450
                outer_transition.assignments(),
13✔
451
                this->inner_loop_.debug_info()
13✔
452
            );
13✔
453
        } else {
25✔
454
            new_outer_loop = &builder.add_for_after(
25✔
455
                outer_scope,
25✔
456
                this->outer_loop_,
25✔
457
                this->inner_loop_.indvar(),
25✔
458
                this->inner_loop_.condition(),
25✔
459
                this->inner_loop_.init(),
25✔
460
                this->inner_loop_.update(),
25✔
461
                outer_transition.assignments(),
25✔
462
                this->inner_loop_.debug_info()
25✔
463
            );
25✔
464
        }
25✔
465

466
        if (outer_map) {
38✔
467
            new_inner_loop = &builder.add_map_after(
17✔
468
                inner_scope,
17✔
469
                this->inner_loop_,
17✔
470
                outer_map->indvar(),
17✔
471
                outer_map->condition(),
17✔
472
                outer_map->init(),
17✔
473
                outer_map->update(),
17✔
474
                outer_map->schedule_type(),
17✔
475
                {},
17✔
476
                this->outer_loop_.debug_info()
17✔
477
            );
17✔
478
        } else {
21✔
479
            new_inner_loop = &builder.add_for_after(
21✔
480
                inner_scope,
21✔
481
                this->inner_loop_,
21✔
482
                this->outer_loop_.indvar(),
21✔
483
                this->outer_loop_.condition(),
21✔
484
                this->outer_loop_.init(),
21✔
485
                this->outer_loop_.update(),
21✔
486
                {},
21✔
487
                this->outer_loop_.debug_info()
21✔
488
            );
21✔
489
        }
21✔
490
    }
38✔
491

492
    // Insert inner loop body into new inner loop
493
    builder.move_children(this->inner_loop_.root(), new_inner_loop->root());
46✔
494

495
    // Insert outer loop body into new outer loop
496
    builder.move_children(this->outer_loop_.root(), new_outer_loop->root());
46✔
497

498
    // Remove old loops
499
    builder.remove_child(new_outer_loop->root(), 0);
46✔
500
    builder.remove_child(outer_scope, index);
46✔
501

502
    analysis_manager.invalidate_all();
46✔
503
    applied_ = true;
46✔
504
    new_outer_loop_ = new_outer_loop;
46✔
505
    new_inner_loop_ = new_inner_loop;
46✔
506
};
46✔
507

508
void LoopInterchange::to_json(nlohmann::json& j) const {
29✔
509
    std::vector<std::string> loop_types;
29✔
510
    for (auto* loop : {&(this->outer_loop_), &(this->inner_loop_)}) {
58✔
511
        if (dynamic_cast<structured_control_flow::For*>(loop)) {
58✔
512
            loop_types.push_back("for");
44✔
513
        } else if (dynamic_cast<structured_control_flow::Map*>(loop)) {
44✔
514
            loop_types.push_back("map");
14✔
515
        } else {
14✔
516
            throw InvalidSDFGException("Unsupported loop type for serialization of loop: " + loop->indvar()->get_name());
×
517
        }
×
518
    }
58✔
519
    j["transformation_type"] = this->name();
29✔
520
    j["subgraph"] = {
29✔
521
        {"0", {{"element_id", this->outer_loop_.element_id()}, {"type", loop_types[0]}}},
29✔
522
        {"1", {{"element_id", this->inner_loop_.element_id()}, {"type", loop_types[1]}}}
29✔
523
    };
29✔
524
};
29✔
525

526
LoopInterchange LoopInterchange::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
3✔
527
    auto outer_loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
3✔
528
    auto inner_loop_id = desc["subgraph"]["1"]["element_id"].get<size_t>();
3✔
529
    auto outer_element = builder.find_element_by_id(outer_loop_id);
3✔
530
    auto inner_element = builder.find_element_by_id(inner_loop_id);
3✔
531
    if (outer_element == nullptr) {
3✔
532
        throw InvalidSDFGException("Element with ID " + std::to_string(outer_loop_id) + " not found.");
×
533
    }
×
534
    if (inner_element == nullptr) {
3✔
535
        throw InvalidSDFGException("Element with ID " + std::to_string(inner_loop_id) + " not found.");
×
536
    }
×
537
    auto outer_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(outer_element);
3✔
538
    if (outer_loop == nullptr) {
3✔
539
        throw InvalidSDFGException("Element with ID " + std::to_string(outer_loop_id) + " is not a StructuredLoop.");
×
540
    }
×
541
    auto inner_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(inner_element);
3✔
542
    if (inner_loop == nullptr) {
3✔
543
        throw InvalidSDFGException("Element with ID " + std::to_string(inner_loop_id) + " is not a StructuredLoop.");
×
544
    }
×
545

546
    return LoopInterchange(*outer_loop, *inner_loop);
3✔
547
};
3✔
548

549
structured_control_flow::StructuredLoop* LoopInterchange::new_outer_loop() const {
×
550
    if (!applied_) {
×
551
        throw InvalidSDFGException("Transformation has not been applied yet.");
×
552
    }
×
553
    return new_outer_loop_;
×
554
};
×
555

556
structured_control_flow::StructuredLoop* LoopInterchange::new_inner_loop() const {
×
557
    if (!applied_) {
×
558
        throw InvalidSDFGException("Transformation has not been applied yet.");
×
559
    }
×
560
    return new_inner_loop_;
×
561
};
×
562

563
} // namespace transformations
564
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc