Skip to content

Extend Solver to handle more min/max cases - #9331

Merged
alexreinking merged 3 commits into
fix-specialize-branch-mergefrom
alexreinking/solve-interval-min-max
Aug 14, 2026
Merged

Extend Solver to handle more min/max cases#9331
alexreinking merged 3 commits into
fix-specialize-branch-mergefrom
alexreinking/solve-interval-min-max

Conversation

@alexreinking

Copy link
Copy Markdown
Member

This PR fixes a performance regression in an Adobe pipeline. Reworking min/extent to min/max in #8858 introduced new expression structures that confounded loop partitioning (which tries to solve if-guards to the loop variable).

A few more fixes along the way:

  • The solver now handles unrelated predicates by trying to prove them. Now (something) && 0 <= 16 won't fail because it "couldn't solve" 0 <= 16 for the interval.
  • The solver now folds x * 0 to 0... makes me wonder when/if we should call the simplifier here.
  • We substitute in lets because we can't see the necessary structure otherwise. The Solver's cache seems sufficient to avoid graph issues. The fuzzer doesn't hang at any rate.

Breaking changes

None.

Checklist

  • Tests added or updated (not required for docs, CI config, or typo fixes)
  • Documentation updated (if public API changed)
  • Python bindings updated (if public API changed)
  • Benchmarks are included here if the change is intended to affect performance.
  • Commits include AI attribution where applicable (see Code of Conduct)

Stack created with GitHub Stacks CLIGive Feedback 💬

solve_for_{inner,outer}_interval (used by loop partitioning) had rewrites
for a Min/Max on the side being solved for (min(a,b) <= c), but none for
a Min/Max on the *other* side (c <= min(a,b)).

This became necessary when #8858 changed the representation of extent.
We now get expressions of the form

    let bound = min(next_vector_boundary, extent) in (idx <= bound)

in GuardWithIf loops where the extent isn't a compile-time multiple of
the vector width. Without these rules, loop partitioning can't remove
these guards in the steady-state.

This PR adds the rule `c <= min(a,b) <=> c<=a && c<=b` (and the analogous
rules for max/>=) to the solver. We also add two supporting rules: folding
`a * 0` to `a`, and handling inequalities that do not mention the solved
variable via `can_prove` (e.g. `x <= 16 && 0 <= 16`).
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.83%. Comparing base (3af222f) to head (c6706d0).

Additional details and impacted files
@@                       Coverage Diff                       @@
##           fix-specialize-branch-merge    #9331      +/-   ##
===============================================================
- Coverage                        69.84%   69.83%   -0.02%     
===============================================================
  Files                              258      258              
  Lines                            78237    78263      +26     
  Branches                         19042    19053      +11     
===============================================================
+ Hits                             54644    54653       +9     
+ Misses                           17849    17834      -15     
- Partials                          5744     5776      +32     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/Solve.cpp
// the second condition should return everything, rather than fail,
// and the rule for && will intersect the LHS with everything,
// leaving the LHS as the final result.
if (Expr cond = le; !expr_uses_var(cond, var)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expr_uses_var does a fresh descent on every LE node. These are unlikely to be nested so this is probably OK, but it might be better if the solver could return "doesn't depend on var" as one of the outcomes

Comment thread src/Solve.cpp
c.accept(&s);
// SolveForInterval's structural rewrites match on the shape of a
// comparison's operands, so they're defeated if an operand is hidden
// behind a let. Inline them first. graph_substitute keeps shared

@abadams abadams Aug 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment could use a tweak. graph_substitute isn't (obviously) called here. Suggest:

substitute_in_all_lets produces a DAG of Exprs, and the solver caches by Expr, so this stays cheap. The result is CSE'd so before being returned, so this expansion is temporary and contained to this function.

@abadams abadams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest a minor comment tweak but otherwise lgtm

@alexreinking
alexreinking merged commit f56ab8c into main Aug 14, 2026
28 checks passed
@alexreinking
alexreinking deleted the alexreinking/solve-interval-min-max branch August 14, 2026 12:28
@abadams

abadams commented Aug 15, 2026

Copy link
Copy Markdown
Member

This actually had a bug I missed. See #9345 for the fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants