• 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

59.34
/opt/src/transformations/loop_condition_normalize.cpp
1
#include "sdfg/transformations/loop_condition_normalize.h"
2

3
#include <stdexcept>
4

5
#include "sdfg/builder/structured_sdfg_builder.h"
6
#include "sdfg/structured_control_flow/map.h"
7
#include "sdfg/symbolic/conjunctive_normal_form.h"
8
#include "sdfg/symbolic/polynomials.h"
9
#include "sdfg/symbolic/symbolic.h"
10

11
/**
12
 * Loop Condition Normalize Transformation Implementation
13
 *
14
 * This transformation normalizes loop conditions through multiple steps:
15
 *
16
 * 1. Pre-normalization: Simplify boolean comparisons
17
 *    - `false == (relational)` → negated relational
18
 *    - `true == (relational)` → relational
19
 *
20
 * 2. Indvar isolation: Ensure bare indvar on LHS of all relationals
21
 *    - `i + offset < bound` → `i < bound - offset`
22
 *    - `bound < i + offset` → `bound - offset < i`
23
 *    (No stride requirement - pure algebraic transformation)
24
 *
25
 * 3. Main normalization: Convert `!=` to `<` or `>` based on stride
26
 *    - stride = +1: `i != N` → `i < N`
27
 *    - stride = -1: `i != N` → `i > N`
28
 *    (Requires unit stride ±1)
29
 *
30
 * 4. Post-normalization: Simplify trivial max/min in bounds
31
 *    - `i < max(init, N)` with loop init=init → `i < N`
32
 *
33
 * The transformation assumes LLVM-style well-formed loops where the bound is
34
 * reachable from init.
35
 */
36

37
namespace sdfg {
38
namespace transformations {
39

40
namespace {
41

42
/**
43
 * Negate a relational expression
44
 * LessThan(a, b) → Ge(a, b)
45
 * StrictLessThan(a, b) → Le(a, b) (actually Gt reversed: b > a means a <= b? No...)
46
 * Actually: Not(a < b) means a >= b
47
 */
48
symbolic::Condition negate_relational(const symbolic::Condition& rel) {
1✔
49
    if (SymEngine::is_a<SymEngine::LessThan>(*rel)) {
1✔
50
        auto lt = SymEngine::rcp_static_cast<const SymEngine::LessThan>(rel);
×
51
        return symbolic::Gt(lt->get_arg1(), lt->get_arg2());
×
52
    }
×
53
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*rel)) {
1✔
54
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(rel);
1✔
55
        return symbolic::Ge(lt->get_arg1(), lt->get_arg2());
1✔
56
    }
1✔
57
    // Fallback: wrap in Not
58
    return symbolic::Not(rel);
×
59
}
1✔
60

61
/**
62
 * Pre-normalization: Simplify `false == (relational)` and `true == (relational)`
63
 * Returns the simplified condition.
64
 */
65
symbolic::Condition simplify_boolean_comparisons(const symbolic::Condition& cond) {
12✔
66
    // Handle Equality: (true == rel) → rel, (false == rel) → Not(rel)
67
    if (SymEngine::is_a<SymEngine::Equality>(*cond)) {
12✔
68
        auto eq = SymEngine::rcp_static_cast<const SymEngine::Equality>(cond);
1✔
69
        auto arg1 = eq->get_arg1();
1✔
70
        auto arg2 = eq->get_arg2();
1✔
71

72
        // Check if one side is true/false and the other is relational
73
        if (SymEngine::is_a_Relational(*arg1) && !SymEngine::is_a_Relational(*arg2)) {
1✔
74
            if (symbolic::is_true(arg2)) {
×
75
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1);
×
76
            } else if (symbolic::is_false(arg2)) {
×
77
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1));
×
78
            }
×
79
        }
×
80
        if (SymEngine::is_a_Relational(*arg2) && !SymEngine::is_a_Relational(*arg1)) {
1✔
81
            if (symbolic::is_true(arg1)) {
1✔
82
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2);
×
83
            } else if (symbolic::is_false(arg1)) {
1✔
84
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2));
1✔
85
            }
1✔
86
        }
1✔
87
    }
1✔
88

89
    // Handle Unequality: (true != rel) → Not(rel), (false != rel) → rel
90
    if (SymEngine::is_a<SymEngine::Unequality>(*cond)) {
11✔
91
        auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(cond);
5✔
92
        auto arg1 = ne->get_arg1();
5✔
93
        auto arg2 = ne->get_arg2();
5✔
94

95
        if (SymEngine::is_a_Relational(*arg1) && !SymEngine::is_a_Relational(*arg2)) {
5✔
96
            if (symbolic::is_true(arg2)) {
×
97
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1));
×
98
            } else if (symbolic::is_false(arg2)) {
×
99
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1);
×
100
            }
×
101
        }
×
102
        if (SymEngine::is_a_Relational(*arg2) && !SymEngine::is_a_Relational(*arg1)) {
5✔
103
            if (symbolic::is_true(arg1)) {
×
104
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2));
×
105
            } else if (symbolic::is_false(arg1)) {
×
106
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2);
×
107
            }
×
108
        }
×
109
    }
5✔
110

111
    // Handle And/Or recursively
112
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
11✔
113
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
114
        auto args = and_cond->get_args();
×
115
        std::vector<symbolic::Condition> new_args;
×
116
        for (const auto& arg : args) {
×
117
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
118
            new_args.push_back(simplify_boolean_comparisons(bool_arg));
×
119
        }
×
120
        symbolic::Condition result = new_args[0];
×
121
        for (size_t i = 1; i < new_args.size(); ++i) {
×
122
            result = symbolic::And(result, new_args[i]);
×
123
        }
×
124
        return result;
×
125
    }
×
126

127
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
11✔
128
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
129
        auto args = or_cond->get_args();
×
130
        std::vector<symbolic::Condition> new_args;
×
131
        for (const auto& arg : args) {
×
132
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
133
            new_args.push_back(simplify_boolean_comparisons(bool_arg));
×
134
        }
×
135
        symbolic::Condition result = new_args[0];
×
136
        for (size_t i = 1; i < new_args.size(); ++i) {
×
137
            result = symbolic::Or(result, new_args[i]);
×
138
        }
×
139
        return result;
×
140
    }
×
141

142
    return cond;
11✔
143
}
11✔
144

145
/**
146
 * Simplify max(init, bound) → bound when one argument equals loop init.
147
 * This is safe because if init <= bound, the loop will terminate properly.
148
 */
149
symbolic::Expression simplify_max_with_init(const symbolic::Expression& expr, const symbolic::Expression& loop_init) {
12✔
150
    if (SymEngine::is_a<SymEngine::Max>(*expr)) {
12✔
151
        auto max_expr = SymEngine::rcp_static_cast<const SymEngine::Max>(expr);
1✔
152
        auto args = max_expr->get_args();
1✔
153

154
        // Find if any argument equals loop_init
155
        for (const auto& arg : args) {
1✔
156
            if (symbolic::eq(arg, loop_init)) {
1✔
157
                // Collect remaining arguments
158
                std::vector<symbolic::Expression> remaining;
1✔
159
                for (const auto& other : args) {
2✔
160
                    if (!symbolic::eq(other, loop_init)) {
2✔
161
                        remaining.push_back(other);
1✔
162
                    }
1✔
163
                }
2✔
164
                if (remaining.size() == 1) {
1✔
165
                    return remaining[0];
1✔
166
                } else if (remaining.size() > 1) {
1✔
167
                    return SymEngine::max(remaining);
×
168
                }
×
169
            }
1✔
170
        }
1✔
171
    }
1✔
172
    return expr;
11✔
173
}
12✔
174

175
/**
176
 * Post-normalization: Simplify bounds containing max(init, ...) patterns
177
 */
178
symbolic::Condition simplify_max_bounds(const symbolic::Condition& cond, const symbolic::Expression& loop_init) {
12✔
179
    // Handle StrictLessThan: i < max(init, N) → i < N
180
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
12✔
181
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
11✔
182
        auto lhs = lt->get_arg1();
11✔
183
        auto rhs = lt->get_arg2();
11✔
184
        auto simplified_rhs = simplify_max_with_init(rhs, loop_init);
11✔
185
        if (!symbolic::eq(rhs, simplified_rhs)) {
11✔
186
            return symbolic::Lt(lhs, simplified_rhs);
1✔
187
        }
1✔
188
    }
11✔
189

190
    // Handle LessThan: i <= max(init, N) → i <= N
191
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
11✔
192
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
1✔
193
        auto lhs = le->get_arg1();
1✔
194
        auto rhs = le->get_arg2();
1✔
195
        auto simplified_rhs = simplify_max_with_init(rhs, loop_init);
1✔
196
        if (!symbolic::eq(rhs, simplified_rhs)) {
1✔
197
            return symbolic::Le(lhs, simplified_rhs);
×
198
        }
×
199
    }
1✔
200

201
    // Handle And/Or recursively
202
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
11✔
203
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
204
        auto args = and_cond->get_args();
×
205
        std::vector<symbolic::Condition> new_args;
×
206
        for (const auto& arg : args) {
×
207
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
208
            new_args.push_back(simplify_max_bounds(bool_arg, loop_init));
×
209
        }
×
210
        symbolic::Condition result = new_args[0];
×
211
        for (size_t i = 1; i < new_args.size(); ++i) {
×
212
            result = symbolic::And(result, new_args[i]);
×
213
        }
×
214
        return result;
×
215
    }
×
216

217
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
11✔
218
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
219
        auto args = or_cond->get_args();
×
220
        std::vector<symbolic::Condition> new_args;
×
221
        for (const auto& arg : args) {
×
222
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
223
            new_args.push_back(simplify_max_bounds(bool_arg, loop_init));
×
224
        }
×
225
        symbolic::Condition result = new_args[0];
×
226
        for (size_t i = 1; i < new_args.size(); ++i) {
×
227
            result = symbolic::Or(result, new_args[i]);
×
228
        }
×
229
        return result;
×
230
    }
×
231

232
    return cond;
11✔
233
}
11✔
234

235
/**
236
 * Check if condition contains boolean comparison patterns (false == rel, true == rel)
237
 */
238
bool has_boolean_comparison_pattern(const symbolic::Condition& cond) {
22✔
239
    if (SymEngine::is_a<SymEngine::Equality>(*cond) || SymEngine::is_a<SymEngine::Unequality>(*cond)) {
22✔
240
        auto rel = SymEngine::rcp_static_cast<const SymEngine::Relational>(cond);
8✔
241
        auto arg1 = rel->get_arg1();
8✔
242
        auto arg2 = rel->get_arg2();
8✔
243

244
        bool arg1_is_bool = symbolic::is_true(arg1) || symbolic::is_false(arg1);
8✔
245
        bool arg2_is_bool = symbolic::is_true(arg2) || symbolic::is_false(arg2);
8✔
246
        bool arg1_is_rel = SymEngine::is_a_Relational(*arg1);
8✔
247
        bool arg2_is_rel = SymEngine::is_a_Relational(*arg2);
8✔
248

249
        if ((arg1_is_bool && arg2_is_rel) || (arg2_is_bool && arg1_is_rel)) {
8✔
250
            return true;
1✔
251
        }
1✔
252
    }
8✔
253

254
    // Check recursively in And/Or
255
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
21✔
256
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
257
        for (const auto& arg : and_cond->get_args()) {
×
258
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
259
            if (has_boolean_comparison_pattern(bool_arg)) {
×
260
                return true;
×
261
            }
×
262
        }
×
263
    }
×
264
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
21✔
265
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
266
        for (const auto& arg : or_cond->get_args()) {
×
267
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
268
            if (has_boolean_comparison_pattern(bool_arg)) {
×
269
                return true;
×
270
            }
×
271
        }
×
272
    }
×
273

274
    return false;
21✔
275
}
21✔
276

277
/**
278
 * Try to isolate indvar in a relational expression.
279
 * Transforms: (indvar + offset) <relop> bound → indvar <relop> (bound - offset)
280
 *             bound <relop> (indvar + offset) → (bound - offset) <relop> indvar
281
 * Returns the original condition if isolation is not applicable.
282
 */
283
symbolic::Condition isolate_indvar_in_relational(const symbolic::Condition& cond, const symbolic::Symbol& indvar) {
12✔
284
    // Helper to extract offset from affine expression with indvar coefficient = 1
285
    auto extract_offset = [&indvar](const symbolic::Expression& expr) -> std::optional<symbolic::Expression> {
12✔
286
        symbolic::SymbolVec syms = {indvar};
12✔
287
        auto poly = symbolic::polynomial(expr, syms);
12✔
288
        if (poly.is_null()) {
12✔
289
            return std::nullopt;
×
290
        }
×
291

292
        auto coeffs = symbolic::affine_coefficients(poly);
12✔
293
        if (coeffs.empty() || coeffs.find(indvar) == coeffs.end()) {
12✔
294
            return std::nullopt;
×
295
        }
×
296

297
        auto coeff = coeffs.at(indvar);
12✔
298
        if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
12✔
299
            return std::nullopt;
×
300
        }
×
301
        auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
12✔
302
        if (coeff_int != 1) {
12✔
303
            return std::nullopt;
×
304
        }
×
305

306
        symbolic::Expression offset = symbolic::zero();
12✔
307
        if (coeffs.count(symbolic::symbol("__daisy_constant__"))) {
12✔
308
            offset = coeffs.at(symbolic::symbol("__daisy_constant__"));
12✔
309
        }
12✔
310
        return offset;
12✔
311
    };
12✔
312

313
    // Helper to process a binary relational
314
    auto process_relational =
12✔
315
        [&](const symbolic::Expression& lhs, const symbolic::Expression& rhs, auto make_same_rel, auto make_flipped_rel
12✔
316
        ) -> symbolic::Condition {
12✔
317
        bool lhs_has_indvar = symbolic::uses(lhs, indvar->get_name());
12✔
318
        bool rhs_has_indvar = symbolic::uses(rhs, indvar->get_name());
12✔
319

320
        // Skip if indvar not present or on both sides
321
        if ((!lhs_has_indvar && !rhs_has_indvar) || (lhs_has_indvar && rhs_has_indvar)) {
12✔
322
            return cond;
×
323
        }
×
324

325
        if (lhs_has_indvar) {
12✔
326
            // (indvar + offset) <relop> rhs → indvar <relop> (rhs - offset)
327
            if (auto offset = extract_offset(lhs)) {
7✔
328
                if (!symbolic::eq(*offset, symbolic::zero())) {
7✔
329
                    auto new_bound = symbolic::expand(symbolic::sub(rhs, *offset));
5✔
330
                    return make_same_rel(indvar, new_bound);
5✔
331
                }
5✔
332
            }
7✔
333
        } else {
7✔
334
            // lhs <relop> (indvar + offset) → (lhs - offset) <relop> indvar
335
            if (auto offset = extract_offset(rhs)) {
5✔
336
                if (!symbolic::eq(*offset, symbolic::zero())) {
5✔
337
                    auto new_bound = symbolic::expand(symbolic::sub(lhs, *offset));
1✔
338
                    return make_flipped_rel(new_bound, indvar);
1✔
339
                }
1✔
340
            }
5✔
341
        }
5✔
342
        return cond;
6✔
343
    };
12✔
344

345
    // Handle StrictLessThan: a < b
346
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
12✔
347
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
6✔
348
        return process_relational(
6✔
349
            lt->get_arg1(),
6✔
350
            lt->get_arg2(),
6✔
351
            [](auto a, auto b) { return symbolic::Lt(a, b); },
6✔
352
            [](auto a, auto b) { return symbolic::Lt(a, b); }
6✔
353
        );
6✔
354
    }
6✔
355

356
    // Handle LessThan (<=): a <= b
357
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
6✔
358
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
1✔
359
        return process_relational(
1✔
360
            le->get_arg1(),
1✔
361
            le->get_arg2(),
1✔
362
            [](auto a, auto b) { return symbolic::Le(a, b); },
1✔
363
            [](auto a, auto b) { return symbolic::Le(a, b); }
1✔
364
        );
1✔
365
    }
1✔
366

367
    // Handle Unequality: a != b
368
    if (SymEngine::is_a<SymEngine::Unequality>(*cond)) {
5✔
369
        auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(cond);
5✔
370
        return process_relational(
5✔
371
            ne->get_arg1(),
5✔
372
            ne->get_arg2(),
5✔
373
            [](auto a, auto b) { return symbolic::Ne(a, b); },
5✔
374
            [](auto a, auto b) { return symbolic::Ne(a, b); }
5✔
375
        );
5✔
376
    }
5✔
377

378
    // Note: > and >= are typically represented as < and <= with swapped args in SymEngine
379
    // but handle them for completeness
380

381
    return cond;
×
382
}
5✔
383

384
/**
385
 * Recursively isolate indvar in all relationals within a condition
386
 */
387
symbolic::Condition isolate_indvar_in_condition(const symbolic::Condition& cond, const symbolic::Symbol& indvar) {
12✔
388
    // Try isolation on this condition first
389
    auto isolated = isolate_indvar_in_relational(cond, indvar);
12✔
390
    if (!symbolic::eq(isolated, cond)) {
12✔
391
        return isolated;
6✔
392
    }
6✔
393

394
    // Handle And recursively
395
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
6✔
396
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
397
        auto args = and_cond->get_args();
×
398
        std::vector<symbolic::Condition> new_args;
×
399
        for (const auto& arg : args) {
×
400
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
401
            new_args.push_back(isolate_indvar_in_condition(bool_arg, indvar));
×
402
        }
×
403
        symbolic::Condition result = new_args[0];
×
404
        for (size_t i = 1; i < new_args.size(); ++i) {
×
405
            result = symbolic::And(result, new_args[i]);
×
406
        }
×
407
        return result;
×
408
    }
×
409

410
    // Handle Or recursively
411
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
6✔
412
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
413
        auto args = or_cond->get_args();
×
414
        std::vector<symbolic::Condition> new_args;
×
415
        for (const auto& arg : args) {
×
416
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
417
            new_args.push_back(isolate_indvar_in_condition(bool_arg, indvar));
×
418
        }
×
419
        symbolic::Condition result = new_args[0];
×
420
        for (size_t i = 1; i < new_args.size(); ++i) {
×
421
            result = symbolic::Or(result, new_args[i]);
×
422
        }
×
423
        return result;
×
424
    }
×
425

426
    return cond;
6✔
427
}
6✔
428

429
/**
430
 * Check if a relational has non-isolated indvar (i + offset <relop> bound)
431
 */
432
bool has_non_isolated_indvar(const symbolic::Condition& cond, const symbolic::Symbol& indvar) {
22✔
433
    auto check_expr = [&indvar](const symbolic::Expression& expr) -> bool {
38✔
434
        if (!symbolic::uses(expr, indvar->get_name())) {
38✔
435
            return false;
17✔
436
        }
17✔
437
        // If it uses indvar but isn't just the indvar, it needs isolation
438
        if (!symbolic::eq(expr, indvar)) {
21✔
439
            // Verify it's affine with coeff=1 (otherwise we can't isolate)
440
            symbolic::SymbolVec syms = {indvar};
12✔
441
            auto poly = symbolic::polynomial(expr, syms);
12✔
442
            if (poly.is_null()) {
12✔
443
                return false;
×
444
            }
×
445
            auto coeffs = symbolic::affine_coefficients(poly);
12✔
446
            if (coeffs.find(indvar) == coeffs.end()) {
12✔
447
                return false;
×
448
            }
×
449
            auto coeff = coeffs.at(indvar);
12✔
450
            if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
12✔
451
                return false;
×
452
            }
×
453
            auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
12✔
454
            if (coeff_int != 1) {
12✔
455
                return false;
7✔
456
            }
7✔
457
            // Has non-zero offset
458
            if (coeffs.count(symbolic::symbol("__daisy_constant__"))) {
5✔
459
                auto offset = coeffs.at(symbolic::symbol("__daisy_constant__"));
5✔
460
                if (!symbolic::eq(offset, symbolic::zero())) {
5✔
461
                    return true;
5✔
462
                }
5✔
463
            }
5✔
464
        }
5✔
465
        return false;
9✔
466
    };
21✔
467

468
    // Check relationals
469
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
22✔
470
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
14✔
471
        return check_expr(lt->get_arg1()) || check_expr(lt->get_arg2());
14✔
472
    }
14✔
473
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
8✔
474
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
×
475
        return check_expr(le->get_arg1()) || check_expr(le->get_arg2());
×
476
    }
×
477
    if (SymEngine::is_a<SymEngine::Unequality>(*cond)) {
8✔
478
        auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(cond);
7✔
479
        return check_expr(ne->get_arg1()) || check_expr(ne->get_arg2());
7✔
480
    }
7✔
481

482
    // Check recursively in And/Or
483
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
1✔
484
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
485
        for (const auto& arg : and_cond->get_args()) {
×
486
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
487
            if (has_non_isolated_indvar(bool_arg, indvar)) {
×
488
                return true;
×
489
            }
×
490
        }
×
491
    }
×
492
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
1✔
493
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
494
        for (const auto& arg : or_cond->get_args()) {
×
495
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
496
            if (has_non_isolated_indvar(bool_arg, indvar)) {
×
497
                return true;
×
498
            }
×
499
        }
×
500
    }
×
501

502
    return false;
1✔
503
}
1✔
504

505
/**
506
 * Check if condition contains max(init, bound) patterns in bounds
507
 */
508
bool has_max_init_pattern(const symbolic::Condition& cond, const symbolic::Expression& loop_init) {
22✔
509
    auto check_expr = [&loop_init](const symbolic::Expression& expr) -> bool {
22✔
510
        if (SymEngine::is_a<SymEngine::Max>(*expr)) {
14✔
511
            auto max_expr = SymEngine::rcp_static_cast<const SymEngine::Max>(expr);
1✔
512
            for (const auto& arg : max_expr->get_args()) {
1✔
513
                if (symbolic::eq(arg, loop_init)) {
1✔
514
                    return true;
1✔
515
                }
1✔
516
            }
1✔
517
        }
1✔
518
        return false;
13✔
519
    };
14✔
520

521
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
22✔
522
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
14✔
523
        if (check_expr(lt->get_arg2())) {
14✔
524
            return true;
1✔
525
        }
1✔
526
    }
14✔
527
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
21✔
528
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
×
529
        if (check_expr(le->get_arg2())) {
×
530
            return true;
×
531
        }
×
532
    }
×
533

534
    // Check recursively in And/Or
535
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
21✔
536
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
537
        for (const auto& arg : and_cond->get_args()) {
×
538
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
539
            if (has_max_init_pattern(bool_arg, loop_init)) {
×
540
                return true;
×
541
            }
×
542
        }
×
543
    }
×
544
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
21✔
545
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
546
        for (const auto& arg : or_cond->get_args()) {
×
547
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
548
            if (has_max_init_pattern(bool_arg, loop_init)) {
×
549
                return true;
×
550
            }
×
551
        }
×
552
    }
×
553

554
    return false;
21✔
555
}
21✔
556

557
} // anonymous namespace
558

559
LoopConditionNormalize::LoopConditionNormalize(structured_control_flow::StructuredLoop& loop) : loop_(loop) {}
22✔
560

561
std::string LoopConditionNormalize::name() const { return "LoopConditionNormalize"; }
×
562

563
bool LoopConditionNormalize::
564
    can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
22✔
565
    // Check for unit stride (±1) - required for != conversion
566
    auto stride = loop_.stride();
22✔
567
    bool has_unit_stride = false;
22✔
568
    if (!stride.is_null()) {
22✔
569
        auto stride_int = stride->as_int();
22✔
570
        has_unit_stride = (stride_int == 1 || stride_int == -1);
22✔
571
    }
22✔
572

573
    auto condition = loop_.condition();
22✔
574
    auto init = loop_.init();
22✔
575

576
    // Check for pattern 1: boolean comparison (false == relational, true == relational)
577
    bool has_boolean_pattern = has_boolean_comparison_pattern(condition);
22✔
578

579
    // Check for pattern 2: max(init, bound) in bounds
580
    bool has_max_pattern = has_max_init_pattern(condition, init);
22✔
581

582
    // Check for pattern 3: non-isolated indvar (i + offset <relop> bound)
583
    bool has_isolation_pattern = has_non_isolated_indvar(condition, loop_.indvar());
22✔
584

585
    // Check for pattern 4: != involving indvar (requires unit stride)
586
    bool has_unequality_pattern = false;
22✔
587
    if (has_unit_stride) {
22✔
588
        symbolic::CNF cnf;
21✔
589
        try {
21✔
590
            cnf = symbolic::conjunctive_normal_form(condition);
21✔
591
        } catch (...) {
21✔
592
            // CNF conversion failed, still check other patterns
593
            cnf = {};
×
594
        }
×
595

596
        auto indvar = loop_.indvar();
21✔
597

598
        for (const auto& clause : cnf) {
21✔
599
            for (const auto& literal : clause) {
21✔
600
                if (SymEngine::is_a<SymEngine::Unequality>(*literal)) {
21✔
601
                    auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(literal);
6✔
602
                    auto lhs = ne->get_arg1();
6✔
603
                    auto rhs = ne->get_arg2();
6✔
604

605
                    bool lhs_has_indvar = symbolic::uses(lhs, indvar->get_name());
6✔
606
                    bool rhs_has_indvar = symbolic::uses(rhs, indvar->get_name());
6✔
607

608
                    if (!lhs_has_indvar && !rhs_has_indvar) {
6✔
609
                        continue;
×
610
                    }
×
611
                    if (lhs_has_indvar && rhs_has_indvar) {
6✔
612
                        continue;
×
613
                    }
×
614

615
                    // Check if affine with coefficient = 1
616
                    auto expr_with_indvar = lhs_has_indvar ? lhs : rhs;
6✔
617
                    symbolic::SymbolVec syms = {indvar};
6✔
618
                    auto poly = symbolic::polynomial(expr_with_indvar, syms);
6✔
619
                    if (poly.is_null()) {
6✔
620
                        continue;
×
621
                    }
×
622

623
                    auto coeffs = symbolic::affine_coefficients(poly);
6✔
624
                    if (coeffs.empty() || coeffs.find(indvar) == coeffs.end()) {
6✔
625
                        continue;
×
626
                    }
×
627

628
                    auto coeff = coeffs.at(indvar);
6✔
629
                    if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
6✔
630
                        continue;
×
631
                    }
×
632
                    auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
6✔
633
                    if (coeff_int != 1) {
6✔
634
                        continue;
1✔
635
                    }
1✔
636

637
                    has_unequality_pattern = true;
5✔
638
                    break;
5✔
639
                }
6✔
640
            }
21✔
641
            if (has_unequality_pattern) {
21✔
642
                break;
5✔
643
            }
5✔
644
        }
21✔
645
    }
21✔
646

647
    return has_boolean_pattern || has_max_pattern || has_isolation_pattern || has_unequality_pattern;
22✔
648
}
22✔
649

650
void LoopConditionNormalize::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
12✔
651
    auto indvar = loop_.indvar();
12✔
652
    auto stride = loop_.stride();
12✔
653
    auto init = loop_.init();
12✔
654

655
    // Step 1: Pre-normalization - simplify boolean comparisons
656
    // e.g., (false == (a < b)) → (a >= b)
657
    symbolic::Condition condition = simplify_boolean_comparisons(loop_.condition());
12✔
658

659
    // Step 2: Isolate indvar in all relationals (no stride requirement)
660
    // e.g., (i + offset < bound) → (i < bound - offset)
661
    condition = isolate_indvar_in_condition(condition, indvar);
12✔
662

663
    // Step 3: Main normalization - convert != to < or > based on stride
664
    // Only if stride is unit (±1)
665
    if (!stride.is_null()) {
12✔
666
        auto stride_int = stride->as_int();
12✔
667
        if (stride_int == 1 || stride_int == -1) {
12✔
668
            // Convert condition to CNF for processing
669
            symbolic::CNF cnf;
12✔
670
            try {
12✔
671
                cnf = symbolic::conjunctive_normal_form(condition);
12✔
672
            } catch (...) {
12✔
673
                cnf = {{condition}};
×
674
            }
×
675

676
            // Build a new CNF with converted literals
677
            symbolic::CNF new_cnf;
12✔
678

679
            for (const auto& clause : cnf) {
12✔
680
                std::vector<symbolic::Condition> new_clause;
12✔
681

682
                for (const auto& literal : clause) {
12✔
683
                    if (!SymEngine::is_a<SymEngine::Unequality>(*literal)) {
12✔
684
                        // Keep non-unequality literals as-is
685
                        new_clause.push_back(literal);
7✔
686
                        continue;
7✔
687
                    }
7✔
688

689
                    auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(literal);
5✔
690
                    auto lhs = ne->get_arg1();
5✔
691
                    auto rhs = ne->get_arg2();
5✔
692

693
                    bool lhs_has_indvar = symbolic::uses(lhs, indvar->get_name());
5✔
694
                    bool rhs_has_indvar = symbolic::uses(rhs, indvar->get_name());
5✔
695

696
                    if (!lhs_has_indvar && !rhs_has_indvar) {
5✔
697
                        new_clause.push_back(literal);
×
698
                        continue;
×
699
                    }
×
700

701
                    if (lhs_has_indvar && rhs_has_indvar) {
5✔
702
                        new_clause.push_back(literal);
×
703
                        continue;
×
704
                    }
×
705

706
                    // Ensure indvar is on LHS
707
                    if (!lhs_has_indvar) {
5✔
708
                        std::swap(lhs, rhs);
4✔
709
                    }
4✔
710

711
                    // Check if affine with coefficient = 1
712
                    symbolic::SymbolVec syms = {indvar};
5✔
713
                    auto poly = symbolic::polynomial(lhs, syms);
5✔
714
                    if (poly.is_null()) {
5✔
715
                        new_clause.push_back(literal);
×
716
                        continue;
×
717
                    }
×
718

719
                    auto coeffs = symbolic::affine_coefficients(poly);
5✔
720
                    if (coeffs.empty() || coeffs.find(indvar) == coeffs.end()) {
5✔
721
                        new_clause.push_back(literal);
×
722
                        continue;
×
723
                    }
×
724

725
                    auto coeff = coeffs.at(indvar);
5✔
726
                    if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
5✔
727
                        new_clause.push_back(literal);
×
728
                        continue;
×
729
                    }
×
730
                    auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
5✔
731
                    if (coeff_int != 1) {
5✔
732
                        new_clause.push_back(literal);
×
733
                        continue;
×
734
                    }
×
735

736
                    // At this point, indvar should already be isolated by step 2
737
                    // Just convert != to </>
738
                    // Convert: stride > 0 → i < bound, stride < 0 → i > bound
739
                    symbolic::Condition new_literal;
5✔
740
                    if (stride_int > 0) {
5✔
741
                        new_literal = symbolic::Lt(indvar, rhs);
4✔
742
                    } else {
4✔
743
                        new_literal = symbolic::Gt(indvar, rhs);
1✔
744
                    }
1✔
745
                    new_clause.push_back(new_literal);
5✔
746
                }
5✔
747

748
                new_cnf.push_back(new_clause);
12✔
749
            }
12✔
750

751
            // Reconstruct condition from CNF
752
            condition = symbolic::__true__();
12✔
753
            for (const auto& clause : new_cnf) {
12✔
754
                if (clause.empty()) {
12✔
755
                    continue;
×
756
                }
×
757
                symbolic::Condition clause_cond = clause[0];
12✔
758
                for (size_t i = 1; i < clause.size(); ++i) {
12✔
759
                    clause_cond = symbolic::Or(clause_cond, clause[i]);
×
760
                }
×
761
                condition = symbolic::And(condition, clause_cond);
12✔
762
            }
12✔
763
        }
12✔
764
    }
12✔
765

766
    // Step 4: Post-normalization - simplify max(init, bound) → bound
767
    condition = simplify_max_bounds(condition, init);
12✔
768

769
    // Update the loop condition
770
    builder.update_loop(loop_, indvar, condition, init, loop_.update());
12✔
771
}
12✔
772

773
void LoopConditionNormalize::to_json(nlohmann::json& j) const {
×
774
    j["transformation_type"] = this->name();
×
775
    j["parameters"] = nlohmann::json::object();
×
776

777
    serializer::JSONSerializer ser_flat(false);
×
UNCOV
778
    j["subgraph"] = nlohmann::json::object();
×
779
    j["subgraph"]["0"] = nlohmann::json::object();
×
780
    ser_flat.serialize_node(j["subgraph"]["0"], loop_);
×
781
}
×
782

783
LoopConditionNormalize LoopConditionNormalize::
UNCOV
784
    from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
×
785
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
×
786

UNCOV
787
    auto element = builder.find_element_by_id(loop_id);
×
788
    if (element == nullptr) {
×
789
        throw std::runtime_error("Element with ID " + std::to_string(loop_id) + " not found.");
×
790
    }
×
791

UNCOV
792
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
×
793
    if (loop == nullptr) {
×
794
        throw std::runtime_error("Element with ID " + std::to_string(loop_id) + " is not a StructuredLoop.");
×
795
    }
×
796

UNCOV
797
    return LoopConditionNormalize(*loop);
×
798
}
×
799

800
} // namespace transformations
801
} // 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