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

daisytuner / docc / 27234721677

09 Jun 2026 08:45PM UTC coverage: 61.393% (+0.1%) from 61.275%
27234721677

Pull #741

github

web-flow
Merge cbcd8499f into aacd50c09
Pull Request #741: replaces MemAccessRangeAnalysis with MemoryLayoutAnalysis

476 of 519 new or added lines in 12 files covered. (91.71%)

36 existing lines in 9 files now uncovered.

35749 of 58230 relevant lines covered (61.39%)

762.3 hits per line

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

96.63
/sdfg/src/analysis/assumptions_analysis.cpp
1
#include "sdfg/analysis/assumptions_analysis.h"
2

3
#include <utility>
4
#include <vector>
5

6
#include "sdfg/analysis/analysis.h"
7
#include "sdfg/analysis/scope_analysis.h"
8
#include "sdfg/analysis/users.h"
9
#include "sdfg/data_flow/access_node.h"
10
#include "sdfg/data_flow/memlet.h"
11
#include "sdfg/structured_control_flow/structured_loop.h"
12
#include "sdfg/symbolic/assumptions.h"
13
#include "sdfg/symbolic/conjunctive_normal_form.h"
14
#include "sdfg/symbolic/polynomials.h"
15
#include "sdfg/symbolic/symbolic.h"
16
#include "sdfg/types/type.h"
17

18
namespace sdfg {
19
namespace analysis {
20

21
namespace {
22

23
// Add per-symbol bounds derived from a single atomic relation
24
// (`LessThan` / `StrictLessThan` / `Equality`) into `branch_assumptions`.
25
// All other clause shapes (logical connectives, `Unequality`, custom
26
// booleans...) are silently skipped: the caller is responsible for
27
// pre-normalizing via CNF and only invoking this on the literals of
28
// single-literal conjuncts.
29
//
30
// Only symbols already known to `outer_assumptions` get bounds (scope
31
// hygiene — don't introduce alien symbols into the branch state).
32
void extract_bound_from_literal(
33
    const symbolic::Condition& cond,
34
    const symbolic::Assumptions& outer_assumptions,
35
    symbolic::Assumptions& branch_assumptions
36
) {
46✔
37
    // Normalize to `delta OP K` where `delta = lhs - rhs`. SymEngine collapses
38
    // `Gt` and `Ge` into swapped `StrictLessThan` / `LessThan`, so we only
39
    // need to recognise the three canonical relational classes (same
40
    // approach as opt/src/transformations/loop_condition_normalize.cpp and
41
    // sdfglib-auto/.../feature_extractor.cpp::visit_condition).
42
    symbolic::Expression delta;
46✔
43
    symbolic::Expression K_le = SymEngine::null; // threshold for `delta <= K_le`
46✔
44
    symbolic::Expression K_ge = SymEngine::null; // threshold for `delta >= K_ge`
46✔
45

46
    if (SymEngine::is_a<SymEngine::LessThan>(*cond)) {
46✔
47
        // a <= b -> delta <= 0.
48
        auto rel = SymEngine::rcp_static_cast<const SymEngine::LessThan>(cond);
16✔
49
        delta = symbolic::sub(rel->get_arg1(), rel->get_arg2());
16✔
50
        K_le = symbolic::zero();
16✔
51
    } else if (SymEngine::is_a<SymEngine::StrictLessThan>(*cond)) {
30✔
52
        // a < b on the integer domain -> delta <= -1.
53
        auto rel = SymEngine::rcp_static_cast<const SymEngine::StrictLessThan>(cond);
14✔
54
        delta = symbolic::sub(rel->get_arg1(), rel->get_arg2());
14✔
55
        K_le = symbolic::integer(-1);
14✔
56
    } else if (SymEngine::is_a<SymEngine::Equality>(*cond)) {
16✔
57
        // a == b -> delta both <= 0 and >= 0.
58
        auto rel = SymEngine::rcp_static_cast<const SymEngine::Equality>(cond);
7✔
59
        delta = symbolic::sub(rel->get_arg1(), rel->get_arg2());
7✔
60
        K_le = symbolic::zero();
7✔
61
        K_ge = symbolic::zero();
7✔
62
    } else {
9✔
63
        return;
9✔
64
    }
9✔
65

66
    // Only emit bounds for literals that constrain a single induction
67
    // variable, where every other symbol in the bound expression is
68
    // provably constant within the scope.
69
    //
70
    // Rationale:
71
    //   * Target must be an indvar (identified by `Assumption::map()`
72
    //     being non-null). We only narrow loop variables — narrowing a
73
    //     non-indvar local variable would require knowing it isn't
74
    //     reassigned inside the branch, which is out of scope here.
75
    //   * Every other symbol in `delta` must be marked `constant()` by
76
    //     `AssumptionsAnalysis` (SDFG-level parameters and outer-loop
77
    //     indvars qualify; locally-written variables do NOT). If a
78
    //     non-constant symbol appears, the bound would be invalidated
79
    //     by writes deeper in the body.
80
    //   * Symbols not in `outer_assumptions` are treated as unknown —
81
    //     skip rather than emit an unverified bound.
82
    //   * Coupling two indvars (e.g. `kh >= 3 - 2*hout` from im2col's
83
    //     `2*hout + kh - 3 >= 0`) is rejected because the RHS contains
84
    //     a non-constant symbol; this also avoids the BoundAnalysis
85
    //     blow-up observed in `CudaTransformIm2colTest.ExplicitSixDimMap`.
86
    symbolic::Symbol target = SymEngine::null;
37✔
87
    for (const auto& sym : symbolic::atoms(delta)) {
56✔
88
        auto it = outer_assumptions.find(sym);
56✔
89
        if (it == outer_assumptions.end()) return;
56✔
90
        bool is_indvar = !it->second.map().is_null();
49✔
91
        if (is_indvar) {
49✔
92
            if (!target.is_null()) return; // two indvars — refuse
48✔
93
            target = sym;
34✔
94
        } else if (!it->second.constant()) {
34✔
NEW
95
            return; // non-constant local — would be invalidated by body writes
×
NEW
96
        }
×
97
    }
49✔
98
    if (target.is_null()) return;
16✔
99

100
    auto neg_delta = symbolic::expand(symbolic::mul(symbolic::integer(-1), delta));
16✔
101

102
    auto try_emit = [&](const symbolic::Expression& K, bool is_lower) {
32✔
103
        if (K.is_null()) return;
32✔
104
        auto neg_K = symbolic::expand(symbolic::mul(symbolic::integer(-1), K));
18✔
105

106
        auto add_bound = [&](const symbolic::Expression& b, bool lower) {
18✔
107
            if (b.is_null()) return false;
18✔
108
            for (const auto& a : symbolic::atoms(b)) {
18✔
109
                if (symbolic::eq(a, target)) return false;
1✔
110
            }
1✔
111
            if (lower) {
18✔
112
                branch_assumptions[target].add_lower_bound(b);
8✔
113
            } else {
10✔
114
                branch_assumptions[target].add_upper_bound(b);
10✔
115
            }
10✔
116
            return true;
18✔
117
        };
18✔
118

119
        if (auto b = symbolic::solve_affine_bound(delta, target, K, is_lower); !b.is_null()) {
18✔
120
            add_bound(b, is_lower);
8✔
121
            return;
8✔
122
        }
8✔
123
        if (auto b = symbolic::solve_affine_bound(neg_delta, target, neg_K, !is_lower); !b.is_null()) {
10✔
124
            add_bound(b, !is_lower);
10✔
125
        }
10✔
126
    };
10✔
127

128
    try_emit(K_le, /*is_lower=*/false);
16✔
129
    try_emit(K_ge, /*is_lower=*/true);
16✔
130
}
16✔
131

132
// Add per-symbol bounds derived from an IfElse branch condition into
133
// `branch_assumptions`. Uses CNF normalization to handle arbitrary boolean
134
// structure (And / Or / Not / nested / De Morgan) uniformly: after CNF the
135
// condition is `clause_1 AND clause_2 AND ... AND clause_n` where each
136
// clause is a disjunction (OR) of literals. Only **single-literal** clauses
137
// are sound to translate into per-symbol bounds — a multi-literal
138
// disjunctive clause `(L1 OR L2)` does not constrain any one symbol.
139
//
140
// CNF conversion can fail on shapes outside its supported grammar
141
// (CNFException). On failure we silently emit no bounds; the analysis stays
142
// sound, just conservative.
143
void extract_assumptions_from_condition(
144
    const symbolic::Condition& cond,
145
    const symbolic::Assumptions& outer_assumptions,
146
    symbolic::Assumptions& branch_assumptions
147
) {
38✔
148
    symbolic::CNF cnf;
38✔
149
    try {
38✔
150
        cnf = symbolic::conjunctive_normal_form(cond);
38✔
151
    } catch (const symbolic::CNFException&) {
38✔
NEW
152
        return;
×
NEW
153
    }
×
154
    for (const auto& clause : cnf) {
52✔
155
        if (clause.size() != 1) continue; // disjunctive — not soundly splittable
52✔
156
        extract_bound_from_literal(clause.front(), outer_assumptions, branch_assumptions);
46✔
157
    }
46✔
158
}
38✔
159

160
// Ensure `branch_assumptions[sym]` exists for every symbol that
161
// `extract_assumptions_from_condition` will add a bound for, so the per-symbol
162
// `Assumption` object is created with the right symbol identity.
163
void ensure_assumption_entries(const symbolic::Condition& cond, symbolic::Assumptions& branch_assumptions) {
38✔
164
    for (const auto& sym : symbolic::atoms(cond)) {
53✔
165
        if (branch_assumptions.find(sym) == branch_assumptions.end()) {
53✔
166
            branch_assumptions.insert({sym, symbolic::Assumption(sym)});
53✔
167
        }
53✔
168
    }
53✔
169
}
38✔
170

171
} // namespace
172

173
AssumptionsAnalysis::AssumptionsAnalysis(StructuredSDFG& sdfg)
174
    : Analysis(sdfg) {
322✔
175

176
      };
322✔
177

178
void AssumptionsAnalysis::run(analysis::AnalysisManager& analysis_manager) {
322✔
179
    this->assumptions_.clear();
322✔
180
    this->assumptions_with_trivial_.clear();
322✔
181
    this->ref_assumptions_.clear();
322✔
182
    this->ref_assumptions_with_trivial_.clear();
322✔
183

184
    this->parameters_.clear();
322✔
185
    this->users_analysis_ = &analysis_manager.get<Users>();
322✔
186

187
    // Determine parameters
188
    this->determine_parameters(analysis_manager);
322✔
189

190
    // Initialize root assumptions with SDFG-level assumptions
191
    this->assumptions_.insert({&sdfg_.root(), this->additional_assumptions_});
322✔
192
    auto& initial = this->assumptions_[&sdfg_.root()];
322✔
193

194
    this->assumptions_with_trivial_.insert({&sdfg_.root(), initial});
322✔
195
    auto& initial_with_trivial = this->assumptions_with_trivial_[&sdfg_.root()];
322✔
196
    for (auto& entry : sdfg_.assumptions()) {
2,017✔
197
        if (initial_with_trivial.find(entry.first) == initial_with_trivial.end()) {
2,017✔
198
            initial_with_trivial.insert({entry.first, entry.second});
2,017✔
199
        } else {
2,017✔
200
            for (auto& lb : entry.second.lower_bounds()) {
×
201
                initial_with_trivial.at(entry.first).add_lower_bound(lb);
×
202
            }
×
203
            for (auto& ub : entry.second.upper_bounds()) {
×
204
                initial_with_trivial.at(entry.first).add_upper_bound(ub);
×
205
            }
×
206
        }
×
207
    }
2,017✔
208

209
    // Traverse and propagate
210
    this->traverse(sdfg_.root(), initial, initial_with_trivial);
322✔
211
};
322✔
212

213
void AssumptionsAnalysis::traverse(
214
    structured_control_flow::ControlFlowNode& current,
215
    const symbolic::Assumptions& outer_assumptions,
216
    const symbolic::Assumptions& outer_assumptions_with_trivial
217
) {
3,269✔
218
    this->propagate_ref(current, outer_assumptions, outer_assumptions_with_trivial);
3,269✔
219

220
    if (auto sequence_stmt = dynamic_cast<structured_control_flow::Sequence*>(&current)) {
3,269✔
221
        for (size_t i = 0; i < sequence_stmt->size(); i++) {
3,270✔
222
            this->traverse(sequence_stmt->at(i).first, outer_assumptions, outer_assumptions_with_trivial);
1,865✔
223
        }
1,865✔
224
    } else if (auto if_else_stmt = dynamic_cast<structured_control_flow::IfElse*>(&current)) {
1,864✔
225
        for (size_t i = 0; i < if_else_stmt->size(); i++) {
63✔
226
            auto& branch_seq = if_else_stmt->at(i).first;
38✔
227
            const auto& condition = if_else_stmt->at(i).second;
38✔
228

229
            // Build per-branch assumption deltas from the case condition.
230
            // Conjunctive structure is split into independent constraints;
231
            // disjunctions / negations contribute nothing (see
232
            // extract_assumptions_from_condition).
233
            symbolic::Assumptions branch_assumptions;
38✔
234
            ensure_assumption_entries(condition, branch_assumptions);
38✔
235
            extract_assumptions_from_condition(condition, outer_assumptions, branch_assumptions);
38✔
236

237
            // Same propagation pattern as traverse_structured_loop: install
238
            // the merged set for the branch sequence, then recurse with the
239
            // installed scope as the new outer scope.
240
            this->propagate(
38✔
241
                const_cast<structured_control_flow::Sequence&>(branch_seq),
38✔
242
                branch_assumptions,
38✔
243
                outer_assumptions,
38✔
244
                outer_assumptions_with_trivial
38✔
245
            );
38✔
246
            this->traverse(
38✔
247
                const_cast<structured_control_flow::Sequence&>(branch_seq),
38✔
248
                this->assumptions_[&branch_seq],
38✔
249
                this->assumptions_with_trivial_[&branch_seq]
38✔
250
            );
38✔
251
        }
38✔
252
    } else if (auto while_stmt = dynamic_cast<structured_control_flow::While*>(&current)) {
1,839✔
253
        this->traverse(while_stmt->root(), outer_assumptions, outer_assumptions_with_trivial);
8✔
254
    } else if (auto loop_stmt = dynamic_cast<structured_control_flow::StructuredLoop*>(&current)) {
1,831✔
255
        this->traverse_structured_loop(loop_stmt, outer_assumptions, outer_assumptions_with_trivial);
1,036✔
256
    } else {
1,036✔
257
        // Other control flow nodes (e.g., Block) do not introduce assumptions or comprise scopes
258
    }
795✔
259
};
3,269✔
260

261
void AssumptionsAnalysis::traverse_structured_loop(
262
    structured_control_flow::StructuredLoop* loop,
263
    const symbolic::Assumptions& outer_assumptions,
264
    const symbolic::Assumptions& outer_assumptions_with_trivial
265
) {
1,036✔
266
    // A structured loop induces assumption for the loop body
267
    auto& body = loop->root();
1,036✔
268
    symbolic::Assumptions body_assumptions;
1,036✔
269

270
    // Define all constant symbols
271
    auto indvar = loop->indvar();
1,036✔
272
    auto update = loop->update();
1,036✔
273
    auto init = loop->init();
1,036✔
274

275
    // By definition, all symbols in the loop condition are constant within the loop body
276
    symbolic::SymbolSet loop_syms = symbolic::atoms(loop->condition());
1,036✔
277
    for (auto& sym : loop_syms) {
2,142✔
278
        body_assumptions.insert({sym, symbolic::Assumption(sym)});
2,142✔
279
        body_assumptions[sym].constant(true);
2,142✔
280
    }
2,142✔
281

282
    // Define map of indvar
283
    body_assumptions[indvar].map(update);
1,036✔
284
    body_assumptions[indvar].constant(true);
1,036✔
285

286
    // Monotonic -> infer bounds on indvar
287
    symbolic::Integer stride = loop->stride();
1,036✔
288
    if (!stride.is_null() && loop->is_monotonic()) {
1,036✔
289
        body_assumptions[indvar].add_lower_bound(init);
1,036✔
290
        body_assumptions[indvar].tight_lower_bound(init);
1,036✔
291

292
        auto ub = loop->canonical_bound_upper();
1,036✔
293
        if (!ub.is_null()) {
1,036✔
294
            // Convert into inclusive bound
295
            symbolic::Expression ub_inclusive;
1,031✔
296
            if (SymEngine::is_a<SymEngine::Min>(*ub)) {
1,031✔
297
                auto min = SymEngine::rcp_static_cast<const SymEngine::Min>(ub);
120✔
298
                std::vector<symbolic::Expression> inclusive_args;
120✔
299
                for (size_t i = 0; i < min->get_args().size(); i++) {
368✔
300
                    auto arg = min->get_args()[i];
248✔
301
                    inclusive_args.push_back(symbolic::sub(arg, symbolic::one()));
248✔
302
                }
248✔
303
                ub_inclusive = inclusive_args.at(0);
120✔
304
                for (size_t i = 1; i < inclusive_args.size(); i++) {
248✔
305
                    ub_inclusive = symbolic::min(ub_inclusive, inclusive_args.at(i));
128✔
306
                }
128✔
307
            } else {
911✔
308
                ub_inclusive = symbolic::sub(ub, symbolic::one());
911✔
309
            }
911✔
310

311
            // ub is a general upper bound
312
            // Compute tight upper bound based on stride
313
            if (symbolic::eq(stride, symbolic::one())) {
1,031✔
314
                // Stride == 1: tight upper bound is simply ub - 1
315
                body_assumptions[indvar].tight_upper_bound(ub_inclusive);
907✔
316
            } else if (!stride.is_null()) {
907✔
317
                // Non-unit stride: tight upper bound = init + idiv(ub_inclusive - init, stride) * stride
318
                // This is the largest value of init + k*stride that is <= ub_inclusive
319
                auto range = symbolic::sub(ub_inclusive, init);
124✔
320
                auto num_steps = symbolic::div(range, stride);
124✔
321
                auto tight_ub = symbolic::add(init, symbolic::mul(num_steps, stride));
124✔
322
                body_assumptions[indvar].tight_upper_bound(tight_ub);
124✔
323
            }
124✔
324

325
            // If combined bound, each arg is also an upper bound
326
            if (SymEngine::is_a<SymEngine::Min>(*ub)) {
1,031✔
327
                auto min = SymEngine::rcp_static_cast<const SymEngine::Min>(ub);
120✔
328
                for (size_t i = 0; i < min->get_args().size(); i++) {
368✔
329
                    auto arg = min->get_args()[i];
248✔
330
                    auto arg_inclusive = symbolic::sub(arg, symbolic::one());
248✔
331
                    body_assumptions[indvar].add_upper_bound(arg_inclusive);
248✔
332
                }
248✔
333
            } else {
911✔
334
                body_assumptions[indvar].add_upper_bound(ub_inclusive);
911✔
335
            }
911✔
336

337
            // Furthermore, we can infer lower bounds for each upper bound's symbol
338
            // For a loop to execute, we need ub > init, so ub >= init + 1
339
            auto min_ub_value = symbolic::add(init, symbolic::one());
1,031✔
340

341
            // Helper to infer lower bound for symbols in an expression
342
            // If expr = coeff * sym + offset and we need expr >= min_ub_value,
343
            // then sym >= (min_ub_value - offset) / coeff
344
            //
345
            // Only infer bounds when min_ub_value is purely constant (no non-parameter
346
            // symbols). When init is symbolic (e.g., a tile indvar), the derived bounds
347
            // reference that symbol (e.g., j_tile0 >= j_tile1-255) and create
348
            // unresolvable chains in BoundAnalysis that prevent delinearization.
349
            bool min_ub_value_is_clean = true;
1,031✔
350
            for (auto& atom : symbolic::atoms(min_ub_value)) {
1,031✔
351
                if (!this->parameters_.contains(atom)) {
176✔
352
                    min_ub_value_is_clean = false;
174✔
353
                    break;
174✔
354
                }
174✔
355
            }
176✔
356

357
            auto infer_symbol_lower_bound = [&](const symbolic::Expression& expr) {
1,159✔
358
                if (!min_ub_value_is_clean) return;
1,159✔
359
                auto atoms = symbolic::atoms(expr);
861✔
360
                for (const auto& sym : atoms) {
861✔
361
                    auto bound = symbolic::solve_affine_bound(expr, sym, min_ub_value, true);
784✔
362
                    if (!bound.is_null()) {
784✔
363
                        body_assumptions[sym].add_lower_bound(bound);
764✔
364
                    }
764✔
365
                }
784✔
366
            };
861✔
367

368
            if (SymEngine::is_a<SymEngine::Min>(*ub)) {
1,031✔
369
                auto min = SymEngine::rcp_static_cast<const SymEngine::Min>(ub);
120✔
370
                for (size_t i = 0; i < min->get_args().size(); i++) {
368✔
371
                    auto arg = min->get_args()[i];
248✔
372
                    infer_symbol_lower_bound(arg);
248✔
373
                }
248✔
374
            } else {
911✔
375
                infer_symbol_lower_bound(ub);
911✔
376
            }
911✔
377
        }
1,031✔
378
    }
1,036✔
379

380
    this->propagate(body, body_assumptions, outer_assumptions, outer_assumptions_with_trivial);
1,036✔
381
    this->traverse(body, this->assumptions_[&body], this->assumptions_with_trivial_[&body]);
1,036✔
382
}
1,036✔
383

384
void AssumptionsAnalysis::propagate(
385
    structured_control_flow::ControlFlowNode& node,
386
    const symbolic::Assumptions& node_assumptions,
387
    const symbolic::Assumptions& outer_assumptions,
388
    const symbolic::Assumptions& outer_assumptions_with_trivial
389
) {
1,074✔
390
    // Propagate assumptions
391
    this->assumptions_.insert({&node, node_assumptions});
1,074✔
392
    auto& propagated_assumptions = this->assumptions_[&node];
1,074✔
393
    for (auto& entry : outer_assumptions) {
2,179✔
394
        if (propagated_assumptions.find(entry.first) == propagated_assumptions.end()) {
2,179✔
395
            // New assumption
396
            propagated_assumptions.insert({entry.first, entry.second});
1,673✔
397
            continue;
1,673✔
398
        }
1,673✔
399

400
        // Merge assumptions from lower scopes
401
        auto& lower_assum = propagated_assumptions[entry.first];
506✔
402

403
        // Add to set of bounds
404
        for (auto ub : entry.second.upper_bounds()) {
506✔
405
            lower_assum.add_upper_bound(ub);
278✔
406
        }
278✔
407
        for (auto lb : entry.second.lower_bounds()) {
506✔
408
            lower_assum.add_lower_bound(lb);
491✔
409
        }
491✔
410

411
        // Set tight bounds
412
        if (lower_assum.tight_upper_bound().is_null()) {
506✔
413
            lower_assum.tight_upper_bound(entry.second.tight_upper_bound());
498✔
414
        }
498✔
415
        if (lower_assum.tight_lower_bound().is_null()) {
506✔
416
            lower_assum.tight_lower_bound(entry.second.tight_lower_bound());
494✔
417
        }
494✔
418

419
        // Set map
420
        if (lower_assum.map().is_null()) {
506✔
421
            lower_assum.map(entry.second.map());
494✔
422
        }
494✔
423

424
        // Set constant
425
        if (!lower_assum.constant()) {
506✔
426
            lower_assum.constant(entry.second.constant());
46✔
427
        }
46✔
428
    }
506✔
429

430
    this->assumptions_with_trivial_.insert({&node, node_assumptions});
1,074✔
431
    auto& assumptions_with_trivial = this->assumptions_with_trivial_[&node];
1,074✔
432
    for (auto& entry : outer_assumptions_with_trivial) {
9,012✔
433
        if (assumptions_with_trivial.find(entry.first) == assumptions_with_trivial.end()) {
9,012✔
434
            // New assumption
435
            assumptions_with_trivial.insert({entry.first, entry.second});
6,819✔
436
            continue;
6,819✔
437
        }
6,819✔
438
        // Merge assumptions from lower scopes
439
        auto& lower_assum = assumptions_with_trivial[entry.first];
2,193✔
440

441
        // Add to set of bounds
442
        for (auto ub : entry.second.upper_bounds()) {
2,471✔
443
            lower_assum.add_upper_bound(ub);
2,471✔
444
        }
2,471✔
445
        for (auto lb : entry.second.lower_bounds()) {
2,512✔
446
            lower_assum.add_lower_bound(lb);
2,512✔
447
        }
2,512✔
448

449
        // Set tight bounds
450
        if (lower_assum.tight_upper_bound().is_null()) {
2,193✔
451
            lower_assum.tight_upper_bound(entry.second.tight_upper_bound());
1,162✔
452
        }
1,162✔
453
        if (lower_assum.tight_lower_bound().is_null()) {
2,193✔
454
            lower_assum.tight_lower_bound(entry.second.tight_lower_bound());
1,157✔
455
        }
1,157✔
456

457
        // Set map
458
        if (lower_assum.map().is_null()) {
2,193✔
459
            lower_assum.map(entry.second.map());
1,157✔
460
        }
1,157✔
461

462
        // Set constant
463
        if (!lower_assum.constant()) {
2,193✔
464
            lower_assum.constant(entry.second.constant());
51✔
465
        }
51✔
466
    }
2,193✔
467
}
1,074✔
468

469
void AssumptionsAnalysis::propagate_ref(
470
    structured_control_flow::ControlFlowNode& node,
471
    const symbolic::Assumptions& outer_assumptions,
472
    const symbolic::Assumptions& outer_assumptions_with_trivial
473
) {
3,269✔
474
    this->ref_assumptions_.insert({&node, &outer_assumptions});
3,269✔
475
    this->ref_assumptions_with_trivial_.insert({&node, &outer_assumptions_with_trivial});
3,269✔
476
}
3,269✔
477

478
void AssumptionsAnalysis::determine_parameters(analysis::AnalysisManager& analysis_manager) {
322✔
479
    for (auto& container : this->sdfg_.arguments()) {
1,188✔
480
        bool readonly = true;
1,188✔
481
        Use not_allowed;
1,188✔
482
        switch (this->sdfg_.type(container).type_id()) {
1,188✔
483
            case types::TypeID::Scalar:
539✔
484
                not_allowed = Use::WRITE;
539✔
485
                break;
539✔
486
            case types::TypeID::Pointer:
406✔
487
                not_allowed = Use::MOVE;
406✔
488
                break;
406✔
489
            case types::TypeID::Array:
242✔
490
            case types::TypeID::Structure:
242✔
491
            case types::TypeID::Reference:
243✔
492
            case types::TypeID::Function:
243✔
493
            case types::TypeID::Tensor:
243✔
494
                continue;
243✔
495
        }
1,188✔
496
        for (auto user : this->users_analysis_->uses(container)) {
2,107✔
497
            if (user->use() == not_allowed) {
2,107✔
498
                readonly = false;
3✔
499
                break;
3✔
500
            }
3✔
501
        }
2,107✔
502
        if (readonly) {
945✔
503
            this->parameters_.insert(symbolic::symbol(container));
942✔
504
        }
942✔
505
    }
945✔
506
}
322✔
507

508
const symbolic::Assumptions& AssumptionsAnalysis::
509
    get(structured_control_flow::ControlFlowNode& node, bool include_trivial_bounds) {
11,530✔
510
    if (include_trivial_bounds) {
11,530✔
511
        return *this->ref_assumptions_with_trivial_[&node];
8,671✔
512
    } else {
8,671✔
513
        return *this->ref_assumptions_[&node];
2,859✔
514
    }
2,859✔
515
}
11,530✔
516

517
const symbolic::SymbolSet& AssumptionsAnalysis::parameters() { return this->parameters_; }
2,781✔
518

519
bool AssumptionsAnalysis::is_parameter(const symbolic::Symbol& container) {
9✔
520
    return this->parameters_.contains(container);
9✔
521
}
9✔
522

523
bool AssumptionsAnalysis::is_parameter(const std::string& container) {
9✔
524
    return this->is_parameter(symbolic::symbol(container));
9✔
525
}
9✔
526

527
} // namespace analysis
528
} // 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