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

daisytuner / docc / 23049466917

13 Mar 2026 11:48AM UTC coverage: 63.722% (+0.2%) from 63.488%
23049466917

push

github

web-flow
Merge pull request #580 from daisytuner/advanced-loop-tiling

Dependence Deltas, LoopInterchange with Fourier-Motzkin and Novel TileFusion Transformation

613 of 845 new or added lines in 8 files covered. (72.54%)

5 existing lines in 3 files now uncovered.

25274 of 39663 relevant lines covered (63.72%)

400.64 hits per line

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

74.29
/sdfg/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) {
2✔
23
    if (deltas_str.empty()) {
2✔
NEW
24
        return false;
×
NEW
25
    }
×
26

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

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

36
    int n_dims = isl_set_dim(deltas, isl_dim_set);
2✔
37
    if (n_dims != 2) {
2✔
NEW
38
        isl_set_free(deltas);
×
NEW
39
        isl_ctx_free(ctx);
×
NEW
40
        return false;
×
NEW
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) }"
2✔
47
                                                   : "{ [x, y] : y < 0 or (y = 0 and x < 0) }";
2✔
48

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

55
    return legal;
2✔
56
}
2✔
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) {
1✔
61
    if (deltas_str.empty()) {
1✔
NEW
62
        return false;
×
NEW
63
    }
×
64

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

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

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

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

87
    return legal;
1✔
88
}
1✔
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) {
11✔
96
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*condition)) {
11✔
97
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(condition);
11✔
98
        auto lhs = lt->get_arg1();
11✔
99
        auto rhs = lt->get_arg2();
11✔
100
        if (symbolic::eq(lhs, indvar)) {
11✔
101
            return rhs;
11✔
102
        }
11✔
103
        // Handle: Lt(indvar + offset, bound) → indvar < bound - offset
NEW
104
        if (symbolic::uses(lhs, indvar->get_name()) && !symbolic::uses(rhs, indvar->get_name())) {
×
NEW
105
            auto offset = symbolic::sub(lhs, indvar);
×
NEW
106
            if (!symbolic::uses(offset, indvar->get_name())) {
×
NEW
107
                return symbolic::sub(rhs, offset);
×
NEW
108
            }
×
NEW
109
        }
×
NEW
110
    }
×
111
    // Handle: And(cond1, cond2, ...) → min of extracted bounds
NEW
112
    if (SymEngine::is_a<SymEngine::And>(*condition)) {
×
NEW
113
        auto conj = SymEngine::rcp_static_cast<const SymEngine::And>(condition);
×
NEW
114
        symbolic::Expression result = SymEngine::null;
×
NEW
115
        for (auto& arg : conj->get_container()) {
×
NEW
116
            auto bound = extract_strict_upper_bound(SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg), indvar);
×
NEW
117
            if (bound == SymEngine::null) return SymEngine::null;
×
NEW
118
            if (result == SymEngine::null) {
×
NEW
119
                result = bound;
×
NEW
120
            } else {
×
NEW
121
                result = symbolic::min(result, bound);
×
NEW
122
            }
×
NEW
123
        }
×
NEW
124
        return result;
×
NEW
125
    }
×
NEW
126
    return SymEngine::null;
×
NEW
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; }
9✔
137
};
138

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

157
      };
108✔
158

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

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

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

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

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

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

218
    // Criterion: Outer loop must not have any outer blocks
219
    if (outer_loop_.root().size() > 1) {
90✔
220
        return false;
17✔
221
    }
17✔
222
    if (outer_loop_.root().at(0).second.assignments().size() > 0) {
73✔
223
        return false;
×
224
    }
×
225
    if (&outer_loop_.root().at(0).first != &inner_loop_) {
73✔
226
        return false;
×
227
    }
×
228
    // Criterion: Any of both loops is a map
229
    if (dynamic_cast<structured_control_flow::Map*>(&outer_loop_) ||
73✔
230
        dynamic_cast<structured_control_flow::Map*>(&inner_loop_)) {
73✔
231
        return true;
58✔
232
    }
58✔
233

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

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

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

248
    // Check outer loop dependencies (2D delta sets: [d_outer, d_inner])
249
    auto& outer_deps = dda.dependencies(outer_loop_);
15✔
250
    for (auto& dep : outer_deps) {
59✔
251
        // Skip dependencies on loop induction variables — structurally safe
252
        if (dep.first == outer_indvar_name || dep.first == inner_indvar_name) {
59✔
253
            continue;
15✔
254
        }
15✔
255
        auto& deltas = dep.second.deltas;
44✔
256
        if (deltas.empty) {
44✔
NEW
257
            continue;
×
NEW
258
        }
×
259
        if (deltas.dimensions.empty()) {
44✔
260
            // No loop dimensions — purely intra-iteration, safe for interchange
261
            continue;
40✔
262
        }
40✔
263
        if (deltas.deltas_str.empty()) {
4✔
264
            // Dependence exists but no isl info — conservative reject
NEW
265
            return false;
×
NEW
266
        }
×
267
        if (deltas.dimensions.size() == 2) {
4✔
268
            // Determine which dimension becomes the new outer (= current inner indvar)
269
            int new_outer_dim = -1;
2✔
270
            for (int d = 0; d < 2; d++) {
4✔
271
                if (deltas.dimensions[d] == inner_indvar_name) {
4✔
272
                    new_outer_dim = d;
2✔
273
                    break;
2✔
274
                }
2✔
275
            }
4✔
276
            if (new_outer_dim < 0) {
2✔
277
                // Can't identify dimension mapping — conservative reject
NEW
278
                return false;
×
NEW
279
            }
×
280
            if (!is_interchange_legal_2d(deltas.deltas_str, new_outer_dim)) {
2✔
281
                return false;
1✔
282
            }
1✔
283
        } else if (deltas.dimensions.size() == 1) {
2✔
284
            // Only outer dimension — after interchange becomes inner, always safe
285
        } else {
2✔
NEW
286
            return false;
×
NEW
287
        }
×
288
    }
4✔
289

290
    // Check inner loop dependencies (1D delta sets: [d_inner])
291
    auto& inner_deps = dda.dependencies(inner_loop_);
14✔
292
    for (auto& dep : inner_deps) {
43✔
293
        if (dep.first == outer_indvar_name || dep.first == inner_indvar_name) {
43✔
NEW
294
            continue;
×
NEW
295
        }
×
296
        auto& deltas = dep.second.deltas;
43✔
297
        if (deltas.empty) {
43✔
NEW
298
            continue;
×
NEW
299
        }
×
300
        if (deltas.dimensions.empty()) {
43✔
301
            continue;
42✔
302
        }
42✔
303
        if (deltas.deltas_str.empty()) {
1✔
NEW
304
            return false;
×
NEW
305
        }
×
306
        if (deltas.dimensions.size() == 1) {
1✔
307
            if (!is_interchange_legal_1d(deltas.deltas_str)) {
1✔
308
                return false;
1✔
309
            }
1✔
310
        } else {
1✔
NEW
311
            return false;
×
NEW
312
        }
×
313
    }
1✔
314

315
    return true;
13✔
316
};
14✔
317

318
void LoopInterchange::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
15✔
319
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
15✔
320
    auto& outer_scope = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&outer_loop_));
15✔
321
    auto& inner_scope = outer_loop_.root();
15✔
322

323
    int index = outer_scope.index(this->outer_loop_);
15✔
324
    auto& outer_transition = outer_scope.at(index).second;
15✔
325

326
    // Add new outer and inner loops
327
    structured_control_flow::StructuredLoop* new_outer_loop = nullptr;
15✔
328
    structured_control_flow::StructuredLoop* new_inner_loop = nullptr;
15✔
329

330
    auto* inner_map = dynamic_cast<structured_control_flow::Map*>(&inner_loop_);
15✔
331
    auto* outer_map = dynamic_cast<structured_control_flow::Map*>(&outer_loop_);
15✔
332

333
    bool dependent = !inner_map && !outer_map &&
15✔
334
                     (symbolic::uses(inner_loop_.init(), outer_loop_.indvar()->get_name()) ||
15✔
335
                      symbolic::uses(inner_loop_.condition(), outer_loop_.indvar()->get_name()));
2✔
336

337
    if (dependent) {
15✔
338
        // Fourier-Motzkin elimination: compute projected and inverted bounds
339
        auto outer_indvar = outer_loop_.indvar();
1✔
340
        auto inner_indvar = inner_loop_.indvar();
1✔
341
        auto outer_init_expr = outer_loop_.init();
1✔
342
        auto outer_bound = extract_strict_upper_bound(outer_loop_.condition(), outer_indvar);
1✔
343
        auto outer_max = symbolic::sub(outer_bound, symbolic::integer(1));
1✔
344

345
        auto inner_init_expr = inner_loop_.init();
1✔
346
        auto inner_bound_expr = extract_strict_upper_bound(inner_loop_.condition(), inner_indvar);
1✔
347

348
        // Project inner bounds for new outer loop:
349
        //   new_init = inner_init(outer_var = outer_init)
350
        //   new_bound = inner_bound(outer_var = outer_max)
351
        auto new_outer_init = symbolic::subs(inner_init_expr, outer_indvar, outer_init_expr);
1✔
352
        auto new_outer_bound = symbolic::subs(inner_bound_expr, outer_indvar, outer_max);
1✔
353
        auto new_outer_cond = symbolic::Lt(inner_indvar, new_outer_bound);
1✔
354

355
        // Invert inner bounds for new inner loop (FM elimination):
356
        //   inner_init  = α*outer_var + b  =>  from y >= α*x + b:  x <= (y-b)/α
357
        //   inner_bound = α*outer_var + d  =>  from y <  α*x + d:  x >  (y-d)/α
358
        // Integer rounding: x < floor((y-b)/α) + 1 and x >= floor((y-d)/α) + 1
359
        auto init_decomp = check_affine(inner_init_expr, outer_indvar);
1✔
360
        auto bound_decomp = check_affine(inner_bound_expr, outer_indvar);
1✔
361
        auto alpha = init_decomp.coefficient; // == bound_decomp.coefficient
1✔
362
        auto b = init_decomp.constant;
1✔
363
        auto d = bound_decomp.constant;
1✔
364

365
        symbolic::Expression lower_from_cond, upper_from_init;
1✔
366
        if (symbolic::eq(alpha, symbolic::integer(1))) {
1✔
367
            // Unit coefficient — avoid introducing idiv(x,1)
NEW
368
            lower_from_cond = symbolic::add(symbolic::sub(inner_indvar, d), symbolic::integer(1));
×
NEW
369
            upper_from_init = symbolic::add(symbolic::sub(inner_indvar, b), symbolic::integer(1));
×
370
        } else {
1✔
371
            // General: floor((y - const) / α) + 1
372
            lower_from_cond = symbolic::add(symbolic::div(symbolic::sub(inner_indvar, d), alpha), symbolic::integer(1));
1✔
373
            upper_from_init = symbolic::add(symbolic::div(symbolic::sub(inner_indvar, b), alpha), symbolic::integer(1));
1✔
374
        }
1✔
375
        auto new_inner_init = symbolic::max(outer_init_expr, lower_from_cond);
1✔
376
        auto new_inner_bound = symbolic::min(outer_bound, upper_from_init);
1✔
377
        auto new_inner_cond = symbolic::Lt(outer_indvar, new_inner_bound);
1✔
378

379
        new_outer_loop = &builder.add_for_after(
1✔
380
            outer_scope,
1✔
381
            this->outer_loop_,
1✔
382
            inner_indvar,
1✔
383
            new_outer_cond,
1✔
384
            new_outer_init,
1✔
385
            this->inner_loop_.update(),
1✔
386
            outer_transition.assignments(),
1✔
387
            this->inner_loop_.debug_info()
1✔
388
        );
1✔
389

390
        new_inner_loop = &builder.add_for_after(
1✔
391
            inner_scope,
1✔
392
            this->inner_loop_,
1✔
393
            outer_indvar,
1✔
394
            new_inner_cond,
1✔
395
            new_inner_init,
1✔
396
            this->outer_loop_.update(),
1✔
397
            {},
1✔
398
            this->outer_loop_.debug_info()
1✔
399
        );
1✔
400
    } else {
14✔
401
        // Standard case: just swap loop headers
402
        if (inner_map) {
14✔
403
            new_outer_loop = &builder.add_map_after(
9✔
404
                outer_scope,
9✔
405
                this->outer_loop_,
9✔
406
                inner_map->indvar(),
9✔
407
                inner_map->condition(),
9✔
408
                inner_map->init(),
9✔
409
                inner_map->update(),
9✔
410
                inner_map->schedule_type(),
9✔
411
                outer_transition.assignments(),
9✔
412
                this->inner_loop_.debug_info()
9✔
413
            );
9✔
414
        } else {
9✔
415
            new_outer_loop = &builder.add_for_after(
5✔
416
                outer_scope,
5✔
417
                this->outer_loop_,
5✔
418
                this->inner_loop_.indvar(),
5✔
419
                this->inner_loop_.condition(),
5✔
420
                this->inner_loop_.init(),
5✔
421
                this->inner_loop_.update(),
5✔
422
                outer_transition.assignments(),
5✔
423
                this->inner_loop_.debug_info()
5✔
424
            );
5✔
425
        }
5✔
426

427
        if (outer_map) {
14✔
428
            new_inner_loop = &builder.add_map_after(
11✔
429
                inner_scope,
11✔
430
                this->inner_loop_,
11✔
431
                outer_map->indvar(),
11✔
432
                outer_map->condition(),
11✔
433
                outer_map->init(),
11✔
434
                outer_map->update(),
11✔
435
                outer_map->schedule_type(),
11✔
436
                {},
11✔
437
                this->outer_loop_.debug_info()
11✔
438
            );
11✔
439
        } else {
11✔
440
            new_inner_loop = &builder.add_for_after(
3✔
441
                inner_scope,
3✔
442
                this->inner_loop_,
3✔
443
                this->outer_loop_.indvar(),
3✔
444
                this->outer_loop_.condition(),
3✔
445
                this->outer_loop_.init(),
3✔
446
                this->outer_loop_.update(),
3✔
447
                {},
3✔
448
                this->outer_loop_.debug_info()
3✔
449
            );
3✔
450
        }
3✔
451
    }
14✔
452

453
    // Insert inner loop body into new inner loop
454
    builder.move_children(this->inner_loop_.root(), new_inner_loop->root());
15✔
455

456
    // Insert outer loop body into new outer loop
457
    builder.move_children(this->outer_loop_.root(), new_outer_loop->root());
15✔
458

459
    // Remove old loops
460
    builder.remove_child(new_outer_loop->root(), 0);
15✔
461
    builder.remove_child(outer_scope, index);
15✔
462

463
    analysis_manager.invalidate_all();
15✔
464
    applied_ = true;
15✔
465
    new_outer_loop_ = new_outer_loop;
15✔
466
    new_inner_loop_ = new_inner_loop;
15✔
467
};
15✔
468

469
void LoopInterchange::to_json(nlohmann::json& j) const {
3✔
470
    std::vector<std::string> loop_types;
3✔
471
    for (auto* loop : {&(this->outer_loop_), &(this->inner_loop_)}) {
6✔
472
        if (dynamic_cast<structured_control_flow::For*>(loop)) {
6✔
473
            loop_types.push_back("for");
×
474
        } else if (dynamic_cast<structured_control_flow::Map*>(loop)) {
6✔
475
            loop_types.push_back("map");
6✔
476
        } else {
6✔
477
            throw InvalidSDFGException("Unsupported loop type for serialization of loop: " + loop->indvar()->get_name());
×
478
        }
×
479
    }
6✔
480
    j["transformation_type"] = this->name();
3✔
481
    j["subgraph"] = {
3✔
482
        {"0", {{"element_id", this->outer_loop_.element_id()}, {"type", loop_types[0]}}},
3✔
483
        {"1", {{"element_id", this->inner_loop_.element_id()}, {"type", loop_types[1]}}}
3✔
484
    };
3✔
485
};
3✔
486

487
LoopInterchange LoopInterchange::from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
3✔
488
    auto outer_loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
3✔
489
    auto inner_loop_id = desc["subgraph"]["1"]["element_id"].get<size_t>();
3✔
490
    auto outer_element = builder.find_element_by_id(outer_loop_id);
3✔
491
    auto inner_element = builder.find_element_by_id(inner_loop_id);
3✔
492
    if (outer_element == nullptr) {
3✔
493
        throw InvalidSDFGException("Element with ID " + std::to_string(outer_loop_id) + " not found.");
×
494
    }
×
495
    if (inner_element == nullptr) {
3✔
496
        throw InvalidSDFGException("Element with ID " + std::to_string(inner_loop_id) + " not found.");
×
497
    }
×
498
    auto outer_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(outer_element);
3✔
499
    if (outer_loop == nullptr) {
3✔
500
        throw InvalidSDFGException("Element with ID " + std::to_string(outer_loop_id) + " is not a StructuredLoop.");
×
501
    }
×
502
    auto inner_loop = dynamic_cast<structured_control_flow::StructuredLoop*>(inner_element);
3✔
503
    if (inner_loop == nullptr) {
3✔
504
        throw InvalidSDFGException("Element with ID " + std::to_string(inner_loop_id) + " is not a StructuredLoop.");
×
505
    }
×
506

507
    return LoopInterchange(*outer_loop, *inner_loop);
3✔
508
};
3✔
509

510
structured_control_flow::StructuredLoop* LoopInterchange::new_outer_loop() const {
×
511
    if (!applied_) {
×
512
        throw InvalidSDFGException("Transformation has not been applied yet.");
×
513
    }
×
514
    return new_outer_loop_;
×
515
};
×
516

517
structured_control_flow::StructuredLoop* LoopInterchange::new_inner_loop() const {
×
518
    if (!applied_) {
×
519
        throw InvalidSDFGException("Transformation has not been applied yet.");
×
520
    }
×
521
    return new_inner_loop_;
×
522
};
×
523

524
} // namespace transformations
525
} // 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