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

daisytuner / docc / 23960224334

03 Apr 2026 08:00PM UTC coverage: 64.73% (-0.09%) from 64.815%
23960224334

Pull #643

github

web-flow
Merge e6a1a4e57 into 951590cfc
Pull Request #643: Replaces LoopNormalization by LoopNormalForm pass

232 of 409 new or added lines in 2 files covered. (56.72%)

5 existing lines in 2 files now uncovered.

29230 of 45157 relevant lines covered (64.73%)

559.12 hits per line

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

56.93
/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. Main normalization: Convert `!=` to `<` or `>` based on stride
21
 *    - stride = +1: `i != N` → `i < N`
22
 *    - stride = -1: `i != N` → `i > N`
23
 *
24
 * 3. Post-normalization: Simplify trivial max/min in bounds
25
 *    - `i < max(init, N)` with loop init=init → `i < N`
26
 *
27
 * The transformation assumes LLVM-style well-formed loops where the bound is
28
 * reachable from init.
29
 */
30

31
namespace sdfg {
32
namespace transformations {
33

34
namespace {
35

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

55
/**
56
 * Pre-normalization: Simplify `false == (relational)` and `true == (relational)`
57
 * Returns the simplified condition.
58
 */
59
symbolic::Condition simplify_boolean_comparisons(const symbolic::Condition& cond) {
7✔
60
    // Handle Equality: (true == rel) → rel, (false == rel) → Not(rel)
61
    if (SymEngine::is_a<SymEngine::Equality>(*cond)) {
7✔
62
        auto eq = SymEngine::rcp_static_cast<const SymEngine::Equality>(cond);
1✔
63
        auto arg1 = eq->get_arg1();
1✔
64
        auto arg2 = eq->get_arg2();
1✔
65

66
        // Check if one side is true/false and the other is relational
67
        if (SymEngine::is_a_Relational(*arg1) && !SymEngine::is_a_Relational(*arg2)) {
1✔
NEW
68
            if (symbolic::is_true(arg2)) {
×
NEW
69
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1);
×
NEW
70
            } else if (symbolic::is_false(arg2)) {
×
NEW
71
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1));
×
NEW
72
            }
×
NEW
73
        }
×
74
        if (SymEngine::is_a_Relational(*arg2) && !SymEngine::is_a_Relational(*arg1)) {
1✔
75
            if (symbolic::is_true(arg1)) {
1✔
NEW
76
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2);
×
77
            } else if (symbolic::is_false(arg1)) {
1✔
78
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2));
1✔
79
            }
1✔
80
        }
1✔
81
    }
1✔
82

83
    // Handle Unequality: (true != rel) → Not(rel), (false != rel) → rel
84
    if (SymEngine::is_a<SymEngine::Unequality>(*cond)) {
6✔
85
        auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(cond);
5✔
86
        auto arg1 = ne->get_arg1();
5✔
87
        auto arg2 = ne->get_arg2();
5✔
88

89
        if (SymEngine::is_a_Relational(*arg1) && !SymEngine::is_a_Relational(*arg2)) {
5✔
NEW
90
            if (symbolic::is_true(arg2)) {
×
NEW
91
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1));
×
NEW
92
            } else if (symbolic::is_false(arg2)) {
×
NEW
93
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg1);
×
NEW
94
            }
×
NEW
95
        }
×
96
        if (SymEngine::is_a_Relational(*arg2) && !SymEngine::is_a_Relational(*arg1)) {
5✔
NEW
97
            if (symbolic::is_true(arg1)) {
×
NEW
98
                return negate_relational(SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2));
×
NEW
99
            } else if (symbolic::is_false(arg1)) {
×
NEW
100
                return SymEngine::rcp_static_cast<const SymEngine::Boolean>(arg2);
×
NEW
101
            }
×
NEW
102
        }
×
103
    }
5✔
104

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

121
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
6✔
NEW
122
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
NEW
123
        auto args = or_cond->get_args();
×
NEW
124
        std::vector<symbolic::Condition> new_args;
×
NEW
125
        for (const auto& arg : args) {
×
NEW
126
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
127
            new_args.push_back(simplify_boolean_comparisons(bool_arg));
×
NEW
128
        }
×
NEW
129
        symbolic::Condition result = new_args[0];
×
NEW
130
        for (size_t i = 1; i < new_args.size(); ++i) {
×
NEW
131
            result = symbolic::Or(result, new_args[i]);
×
NEW
132
        }
×
NEW
133
        return result;
×
NEW
134
    }
×
135

136
    return cond;
6✔
137
}
6✔
138

139
/**
140
 * Simplify max(init, bound) → bound when one argument equals loop init.
141
 * This is safe because if init <= bound, the loop will terminate properly.
142
 */
143
symbolic::Expression simplify_max_with_init(const symbolic::Expression& expr, const symbolic::Expression& loop_init) {
7✔
144
    if (SymEngine::is_a<SymEngine::Max>(*expr)) {
7✔
145
        auto max_expr = SymEngine::rcp_static_cast<const SymEngine::Max>(expr);
1✔
146
        auto args = max_expr->get_args();
1✔
147

148
        // Find if any argument equals loop_init
149
        for (const auto& arg : args) {
1✔
150
            if (symbolic::eq(arg, loop_init)) {
1✔
151
                // Collect remaining arguments
152
                std::vector<symbolic::Expression> remaining;
1✔
153
                for (const auto& other : args) {
2✔
154
                    if (!symbolic::eq(other, loop_init)) {
2✔
155
                        remaining.push_back(other);
1✔
156
                    }
1✔
157
                }
2✔
158
                if (remaining.size() == 1) {
1✔
159
                    return remaining[0];
1✔
160
                } else if (remaining.size() > 1) {
1✔
NEW
161
                    return SymEngine::max(remaining);
×
NEW
162
                }
×
163
            }
1✔
164
        }
1✔
165
    }
1✔
166
    return expr;
6✔
167
}
7✔
168

169
/**
170
 * Post-normalization: Simplify bounds containing max(init, ...) patterns
171
 */
172
symbolic::Condition simplify_max_bounds(const symbolic::Condition& cond, const symbolic::Expression& loop_init) {
7✔
173
    // Handle StrictLessThan: i < max(init, N) → i < N
174
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
7✔
175
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
6✔
176
        auto lhs = lt->get_arg1();
6✔
177
        auto rhs = lt->get_arg2();
6✔
178
        auto simplified_rhs = simplify_max_with_init(rhs, loop_init);
6✔
179
        if (!symbolic::eq(rhs, simplified_rhs)) {
6✔
180
            return symbolic::Lt(lhs, simplified_rhs);
1✔
181
        }
1✔
182
    }
6✔
183

184
    // Handle LessThan: i <= max(init, N) → i <= N
185
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
6✔
186
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
1✔
187
        auto lhs = le->get_arg1();
1✔
188
        auto rhs = le->get_arg2();
1✔
189
        auto simplified_rhs = simplify_max_with_init(rhs, loop_init);
1✔
190
        if (!symbolic::eq(rhs, simplified_rhs)) {
1✔
NEW
191
            return symbolic::Le(lhs, simplified_rhs);
×
NEW
192
        }
×
193
    }
1✔
194

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

211
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
6✔
NEW
212
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
NEW
213
        auto args = or_cond->get_args();
×
NEW
214
        std::vector<symbolic::Condition> new_args;
×
NEW
215
        for (const auto& arg : args) {
×
NEW
216
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
217
            new_args.push_back(simplify_max_bounds(bool_arg, loop_init));
×
NEW
218
        }
×
NEW
219
        symbolic::Condition result = new_args[0];
×
NEW
220
        for (size_t i = 1; i < new_args.size(); ++i) {
×
NEW
221
            result = symbolic::Or(result, new_args[i]);
×
NEW
222
        }
×
NEW
223
        return result;
×
NEW
224
    }
×
225

226
    return cond;
6✔
227
}
6✔
228

229
/**
230
 * Check if condition contains boolean comparison patterns (false == rel, true == rel)
231
 */
232
bool has_boolean_comparison_pattern(const symbolic::Condition& cond) {
36✔
233
    if (SymEngine::is_a<SymEngine::Equality>(*cond) || SymEngine::is_a<SymEngine::Unequality>(*cond)) {
36✔
234
        auto rel = SymEngine::rcp_static_cast<const SymEngine::Relational>(cond);
8✔
235
        auto arg1 = rel->get_arg1();
8✔
236
        auto arg2 = rel->get_arg2();
8✔
237

238
        bool arg1_is_bool = symbolic::is_true(arg1) || symbolic::is_false(arg1);
8✔
239
        bool arg2_is_bool = symbolic::is_true(arg2) || symbolic::is_false(arg2);
8✔
240
        bool arg1_is_rel = SymEngine::is_a_Relational(*arg1);
8✔
241
        bool arg2_is_rel = SymEngine::is_a_Relational(*arg2);
8✔
242

243
        if ((arg1_is_bool && arg2_is_rel) || (arg2_is_bool && arg1_is_rel)) {
8✔
244
            return true;
1✔
245
        }
1✔
246
    }
8✔
247

248
    // Check recursively in And/Or
249
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
35✔
NEW
250
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
NEW
251
        for (const auto& arg : and_cond->get_args()) {
×
NEW
252
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
253
            if (has_boolean_comparison_pattern(bool_arg)) {
×
NEW
254
                return true;
×
NEW
255
            }
×
NEW
256
        }
×
NEW
257
    }
×
258
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
35✔
NEW
259
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
NEW
260
        for (const auto& arg : or_cond->get_args()) {
×
NEW
261
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
262
            if (has_boolean_comparison_pattern(bool_arg)) {
×
NEW
263
                return true;
×
NEW
264
            }
×
NEW
265
        }
×
NEW
266
    }
×
267

268
    return false;
35✔
269
}
35✔
270

271
/**
272
 * Check if condition contains max(init, bound) patterns in bounds
273
 */
274
bool has_max_init_pattern(const symbolic::Condition& cond, const symbolic::Expression& loop_init) {
36✔
275
    auto check_expr = [&loop_init](const symbolic::Expression& expr) -> bool {
36✔
276
        if (SymEngine::is_a<SymEngine::Max>(*expr)) {
28✔
277
            auto max_expr = SymEngine::rcp_static_cast<const SymEngine::Max>(expr);
1✔
278
            for (const auto& arg : max_expr->get_args()) {
1✔
279
                if (symbolic::eq(arg, loop_init)) {
1✔
280
                    return true;
1✔
281
                }
1✔
282
            }
1✔
283
        }
1✔
284
        return false;
27✔
285
    };
28✔
286

287
    if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
36✔
288
        auto lt = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
28✔
289
        if (check_expr(lt->get_arg2())) {
28✔
290
            return true;
1✔
291
        }
1✔
292
    }
28✔
293
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
35✔
NEW
294
        auto le = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
×
NEW
295
        if (check_expr(le->get_arg2())) {
×
NEW
296
            return true;
×
NEW
297
        }
×
NEW
298
    }
×
299

300
    // Check recursively in And/Or
301
    if (SymEngine::is_a<SymEngine::And>(*cond)) {
35✔
NEW
302
        auto and_cond = SymEngine::rcp_static_cast<const SymEngine::And>(cond);
×
NEW
303
        for (const auto& arg : and_cond->get_args()) {
×
NEW
304
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
305
            if (has_max_init_pattern(bool_arg, loop_init)) {
×
NEW
306
                return true;
×
NEW
307
            }
×
NEW
308
        }
×
NEW
309
    }
×
310
    if (SymEngine::is_a<SymEngine::Or>(*cond)) {
35✔
NEW
311
        auto or_cond = SymEngine::rcp_static_cast<const SymEngine::Or>(cond);
×
NEW
312
        for (const auto& arg : or_cond->get_args()) {
×
NEW
313
            auto bool_arg = SymEngine::rcp_dynamic_cast<const SymEngine::Boolean>(arg);
×
NEW
314
            if (has_max_init_pattern(bool_arg, loop_init)) {
×
NEW
315
                return true;
×
NEW
316
            }
×
NEW
317
        }
×
NEW
318
    }
×
319

320
    return false;
35✔
321
}
35✔
322

323
} // anonymous namespace
324

325
LoopConditionNormalize::LoopConditionNormalize(structured_control_flow::StructuredLoop& loop) : loop_(loop) {}
36✔
326

NEW
327
std::string LoopConditionNormalize::name() const { return "LoopConditionNormalize"; }
×
328

329
bool LoopConditionNormalize::
330
    can_be_applied(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
36✔
331
    // Check for unit stride (±1) - required for != conversion
332
    auto stride = loop_.stride();
36✔
333
    bool has_unit_stride = false;
36✔
334
    if (!stride.is_null()) {
36✔
335
        auto stride_int = stride->as_int();
36✔
336
        has_unit_stride = (stride_int == 1 || stride_int == -1);
36✔
337
    }
36✔
338

339
    auto condition = loop_.condition();
36✔
340
    auto init = loop_.init();
36✔
341

342
    // Check for pattern 1: boolean comparison (false == relational, true == relational)
343
    bool has_boolean_pattern = has_boolean_comparison_pattern(condition);
36✔
344

345
    // Check for pattern 2: max(init, bound) in bounds
346
    bool has_max_pattern = has_max_init_pattern(condition, init);
36✔
347

348
    // Check for pattern 3: != involving indvar (requires unit stride)
349
    bool has_unequality_pattern = false;
36✔
350
    if (has_unit_stride) {
36✔
351
        symbolic::CNF cnf;
35✔
352
        try {
35✔
353
            cnf = symbolic::conjunctive_normal_form(condition);
35✔
354
        } catch (...) {
35✔
355
            // CNF conversion failed, still check other patterns
NEW
356
            cnf = {};
×
NEW
357
        }
×
358

359
        auto indvar = loop_.indvar();
35✔
360

361
        for (const auto& clause : cnf) {
35✔
362
            for (const auto& literal : clause) {
35✔
363
                if (SymEngine::is_a<SymEngine::Unequality>(*literal)) {
35✔
364
                    auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(literal);
6✔
365
                    auto lhs = ne->get_arg1();
6✔
366
                    auto rhs = ne->get_arg2();
6✔
367

368
                    bool lhs_has_indvar = symbolic::uses(lhs, indvar->get_name());
6✔
369
                    bool rhs_has_indvar = symbolic::uses(rhs, indvar->get_name());
6✔
370

371
                    if (!lhs_has_indvar && !rhs_has_indvar) {
6✔
NEW
372
                        continue;
×
NEW
373
                    }
×
374
                    if (lhs_has_indvar && rhs_has_indvar) {
6✔
NEW
375
                        continue;
×
NEW
376
                    }
×
377

378
                    // Check if affine with coefficient = 1
379
                    auto expr_with_indvar = lhs_has_indvar ? lhs : rhs;
6✔
380
                    symbolic::SymbolVec syms = {indvar};
6✔
381
                    auto poly = symbolic::polynomial(expr_with_indvar, syms);
6✔
382
                    if (poly.is_null()) {
6✔
NEW
383
                        continue;
×
NEW
384
                    }
×
385

386
                    auto coeffs = symbolic::affine_coefficients(poly, syms);
6✔
387
                    if (coeffs.empty() || coeffs.find(indvar) == coeffs.end()) {
6✔
NEW
388
                        continue;
×
NEW
389
                    }
×
390

391
                    auto coeff = coeffs.at(indvar);
6✔
392
                    if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
6✔
NEW
393
                        continue;
×
NEW
394
                    }
×
395
                    auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
6✔
396
                    if (coeff_int != 1) {
6✔
397
                        continue;
1✔
398
                    }
1✔
399

400
                    has_unequality_pattern = true;
5✔
401
                    break;
5✔
402
                }
6✔
403
            }
35✔
404
            if (has_unequality_pattern) {
35✔
405
                break;
5✔
406
            }
5✔
407
        }
35✔
408
    }
35✔
409

410
    return has_boolean_pattern || has_max_pattern || has_unequality_pattern;
36✔
411
}
36✔
412

413
void LoopConditionNormalize::apply(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
7✔
414
    auto indvar = loop_.indvar();
7✔
415
    auto stride = loop_.stride();
7✔
416
    auto init = loop_.init();
7✔
417

418
    // Step 1: Pre-normalization - simplify boolean comparisons
419
    // e.g., (false == (a < b)) → (a >= b)
420
    symbolic::Condition condition = simplify_boolean_comparisons(loop_.condition());
7✔
421

422
    // Step 2: Main normalization - convert != to < or > based on stride
423
    // Only if stride is unit (±1)
424
    if (!stride.is_null()) {
7✔
425
        auto stride_int = stride->as_int();
7✔
426
        if (stride_int == 1 || stride_int == -1) {
7✔
427
            // Convert condition to CNF for processing
428
            symbolic::CNF cnf;
7✔
429
            try {
7✔
430
                cnf = symbolic::conjunctive_normal_form(condition);
7✔
431
            } catch (...) {
7✔
NEW
432
                cnf = {{condition}};
×
NEW
433
            }
×
434

435
            // Build a new CNF with converted literals
436
            symbolic::CNF new_cnf;
7✔
437

438
            for (const auto& clause : cnf) {
7✔
439
                std::vector<symbolic::Condition> new_clause;
7✔
440

441
                for (const auto& literal : clause) {
7✔
442
                    if (!SymEngine::is_a<SymEngine::Unequality>(*literal)) {
7✔
443
                        // Keep non-unequality literals as-is
444
                        new_clause.push_back(literal);
2✔
445
                        continue;
2✔
446
                    }
2✔
447

448
                    auto ne = SymEngine::rcp_static_cast<const SymEngine::Unequality>(literal);
5✔
449
                    auto lhs = ne->get_arg1();
5✔
450
                    auto rhs = ne->get_arg2();
5✔
451

452
                    bool lhs_has_indvar = symbolic::uses(lhs, indvar->get_name());
5✔
453
                    bool rhs_has_indvar = symbolic::uses(rhs, indvar->get_name());
5✔
454

455
                    if (!lhs_has_indvar && !rhs_has_indvar) {
5✔
NEW
456
                        new_clause.push_back(literal);
×
NEW
457
                        continue;
×
NEW
458
                    }
×
459

460
                    if (lhs_has_indvar && rhs_has_indvar) {
5✔
NEW
461
                        new_clause.push_back(literal);
×
NEW
462
                        continue;
×
NEW
463
                    }
×
464

465
                    // Ensure indvar is on LHS
466
                    if (!lhs_has_indvar) {
5✔
467
                        std::swap(lhs, rhs);
4✔
468
                    }
4✔
469

470
                    // Check if affine with coefficient = 1
471
                    symbolic::SymbolVec syms = {indvar};
5✔
472
                    auto poly = symbolic::polynomial(lhs, syms);
5✔
473
                    if (poly.is_null()) {
5✔
NEW
474
                        new_clause.push_back(literal);
×
NEW
475
                        continue;
×
NEW
476
                    }
×
477

478
                    auto coeffs = symbolic::affine_coefficients(poly, syms);
5✔
479
                    if (coeffs.empty() || coeffs.find(indvar) == coeffs.end()) {
5✔
NEW
480
                        new_clause.push_back(literal);
×
NEW
481
                        continue;
×
NEW
482
                    }
×
483

484
                    auto coeff = coeffs.at(indvar);
5✔
485
                    if (!SymEngine::is_a<SymEngine::Integer>(*coeff)) {
5✔
NEW
486
                        new_clause.push_back(literal);
×
NEW
487
                        continue;
×
NEW
488
                    }
×
489
                    auto coeff_int = SymEngine::rcp_static_cast<const SymEngine::Integer>(coeff)->as_int();
5✔
490
                    if (coeff_int != 1) {
5✔
NEW
491
                        new_clause.push_back(literal);
×
NEW
492
                        continue;
×
NEW
493
                    }
×
494

495
                    symbolic::Expression offset = symbolic::zero();
5✔
496
                    if (coeffs.count(symbolic::symbol("__daisy_constant__"))) {
5✔
497
                        offset = coeffs.at(symbolic::symbol("__daisy_constant__"));
5✔
498
                    }
5✔
499

500
                    // i + offset != rhs → i != (rhs - offset)
501
                    auto normalized_bound = symbolic::expand(symbolic::sub(rhs, offset));
5✔
502

503
                    // Convert: stride > 0 → i < bound, stride < 0 → i > bound
504
                    symbolic::Condition new_literal;
5✔
505
                    if (stride_int > 0) {
5✔
506
                        new_literal = symbolic::Lt(indvar, normalized_bound);
4✔
507
                    } else {
4✔
508
                        new_literal = symbolic::Gt(indvar, normalized_bound);
1✔
509
                    }
1✔
510
                    new_clause.push_back(new_literal);
5✔
511
                }
5✔
512

513
                new_cnf.push_back(new_clause);
7✔
514
            }
7✔
515

516
            // Reconstruct condition from CNF
517
            condition = symbolic::__true__();
7✔
518
            for (const auto& clause : new_cnf) {
7✔
519
                if (clause.empty()) {
7✔
NEW
520
                    continue;
×
NEW
521
                }
×
522
                symbolic::Condition clause_cond = clause[0];
7✔
523
                for (size_t i = 1; i < clause.size(); ++i) {
7✔
NEW
524
                    clause_cond = symbolic::Or(clause_cond, clause[i]);
×
NEW
525
                }
×
526
                condition = symbolic::And(condition, clause_cond);
7✔
527
            }
7✔
528
        }
7✔
529
    }
7✔
530

531
    // Step 3: Post-normalization - simplify max(init, bound) → bound
532
    condition = simplify_max_bounds(condition, init);
7✔
533

534
    // Update the loop condition
535
    builder.update_loop(loop_, indvar, condition, init, loop_.update());
7✔
536
}
7✔
537

NEW
538
void LoopConditionNormalize::to_json(nlohmann::json& j) const {
×
NEW
539
    std::string loop_type = "for";
×
NEW
540
    if (dynamic_cast<const structured_control_flow::Map*>(&loop_)) {
×
NEW
541
        loop_type = "map";
×
NEW
542
    }
×
543

NEW
544
    j["transformation_type"] = this->name();
×
NEW
545
    j["subgraph"] = {{"0", {{"element_id", loop_.element_id()}, {"type", loop_type}}}};
×
NEW
546
    j["parameters"] = nlohmann::json::object();
×
NEW
547
}
×
548

549
LoopConditionNormalize LoopConditionNormalize::
NEW
550
    from_json(builder::StructuredSDFGBuilder& builder, const nlohmann::json& desc) {
×
NEW
551
    auto loop_id = desc["subgraph"]["0"]["element_id"].get<size_t>();
×
552

NEW
553
    auto element = builder.find_element_by_id(loop_id);
×
NEW
554
    if (element == nullptr) {
×
NEW
555
        throw std::runtime_error("Element with ID " + std::to_string(loop_id) + " not found.");
×
NEW
556
    }
×
557

NEW
558
    auto loop = dynamic_cast<structured_control_flow::StructuredLoop*>(element);
×
NEW
559
    if (loop == nullptr) {
×
NEW
560
        throw std::runtime_error("Element with ID " + std::to_string(loop_id) + " is not a StructuredLoop.");
×
NEW
561
    }
×
562

NEW
563
    return LoopConditionNormalize(*loop);
×
NEW
564
}
×
565

566
} // namespace transformations
567
} // 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