Skip to content

compiler: Fix two guard bugs exposed by buffering - #3061

Open
ggorman wants to merge 7 commits into
mainfrom
fix-buffering-guards
Open

ggorman wants to merge 7 commits into
mainfrom
fix-buffering-guards

Conversation

@ggorman

@ggorman ggorman commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Two guard bugs that buffering exposes, each with a test against opt='noop':

  1. if (false) was emitted. A condition SymPy folds to false (e.g. time < 0) printed if (false), which is not C99 without stdbool.h: a compile error. True and false now print as 1 and 0.
  2. {time: true} guards. time >= 0 folds to true and gave a Cluster a guard distinct from no guard, so buffering created one buffer per "guard" for equivalent accesses. Guards now never sets a guard that reduces to true, whatever the entry point.

Tests: test_dimension.py::TestConditionalDimension::test_false_condition, test_buffering.py::test_true_condition_single_buffer, test_ir.py::TestGuards::test_true_never_set. Each fails without its source change.

An earlier GuardFactorNe change was dropped (4d820ac): no real program reaches a negated factor guard.

First of three stacked PRs: this one, then #3062 (copy-back skip rule) and #3063 (stale reads and lost writes).

🤖 Generated with Claude Code

ggorman and others added 3 commits October 16, 2026 10:49
GuardFactorNe inherited CondEq through GuardFactor ahead of CondNe, so it
was an equality and printed `==`. Negating a subsampling guard thus
reproduced the guard itself, selecting the timesteps on the subsampled
grid instead of those off it
A condition SymPy folds to `false`, e.g. `time < 0`, was printed as
`if (false)`, which is not C99 without `stdbool.h` and failed to compile
A condition such as `time >= 0` folds to `true` (`time` is nonnegative)
and gave a Cluster the guard {time: true}, distinct from no guard.
Buffering then created one buffer per guard for equivalent accesses
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.14815% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 84.23%. Comparing base (2707ac9) to head (4d820ac).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
devito/passes/iet/languages/C.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3061   +/-   ##
=======================================
  Coverage   84.23%   84.23%           
=======================================
  Files         258      258           
  Lines       57070    57125   +55     
  Branches     4874     4877    +3     
=======================================
+ Hits        48072    48122   +50     
- Misses       8170     8174    +4     
- Partials      828      829    +1     
Flag Coverage Δ
pytest-gpu-aomp-amdgpuX 68.51% <71.42%> (+<0.01%) ⬆️
pytest-gpu-gcc- 79.02% <98.14%> (+0.01%) ⬆️
pytest-gpu-icx- 78.94% <98.14%> (+0.01%) ⬆️
pytest-gpu-nvc-nvidiaX 69.08% <71.42%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

@mloubout mloubout left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

seems sane

Comment thread devito/ir/clusters/algorithms.py Outdated
guards = {d: mode(*v, evaluate=False) for d, v in guards.items()}

# A condition folded to `true` (e.g., `time >= 0`) imposes no guard
guards = {d: v for d, v in guards.items() if v is not sympy.true}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should rather be fixed directly in class Guards, within devito/ir/support/guards.py: a guard, along any Dimension, should never be set (being pointless) if its value reduces to sympy.true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 4c8c1cd: Guards.__init__ now drops any entry equal to sympy.true, so no entry point (constructor, andg, xandg, impose, subs, ...) can set one, and the filter in the guard pass is gone. Added TestGuards::test_true_never_set; the buffering test still covers the original case.

Drop such guards in `Guards` itself, at construction, rather than in the
`guard` pass, so that no entry point (`andg`, `xandg`, `impose`, `subs`,
...) can set one
Comment thread tests/test_dimension.py Outdated

ct = ConditionalDimension(name='ct', parent=time, factor=2)
cn = ConditionalDimension(name='cn', parent=time,
condition=Not(GuardFactor(ct)), relation='strict')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GuardFactor should not be used at this level this is an internal object, this needs to be a user API def, like Not(CondEq(...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point, fixed in 763b8b2. GuardFactor is only ever built by the compiler from a ConditionalDimension(factor=...), and its negation only arises in buffering, when a copy-back is overwritten by a later one guarded by the factor dimension — so the test now goes through that: two writes to the same usave slot, one at the last timestep and one under factor=2, noop vs buffering (test_buffering.py::test_negated_factor_guard). Fails on main with if (time == 7 && time%tsubf == 0), passes with != 0. The old test is gone from test_dimension.py.

The negated subsampling guard is reached through buffering, when a copy-back
is overwritten by a later one under a factor-based ConditionalDimension, so
test it there instead of constructing the internal GuardFactor
Comment thread tests/test_buffering.py Outdated
assert np.allclose(h.data[0], 12.)


def test_negated_factor_guard():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that's an abuse of the API,

  • the usave isn't defined on tsub but on time
  • It saves two different values at the same steps which doesn't make sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, it was contrived. I checked it against real code (recipes, devitohorizon, the devito examples and the devitopro demos/tests): every snapshot schedule saves on the subsampled dimension with a single writer, and the only same-slot two-writer case is the last-step save in your test_multi_cond_same_slot, which negates the CondEq guard, not the factor one, and is unaffected by the GuardFactorNe change. So nothing real reaches a negated factor guard, and I've dropped both that change and the test (4d820ac). The PR is now just the true/false guard fixes.

A ConditionalDimension whose condition folds to true must yield no
Conditional in the generated code, under any relation
No user program reaches a negated factor guard: real snapshot schedules
save on the subsampled dimension with a single writer, and the same-slot
last-step save negates a CondEq instead. The test needed a contrived
program to get there, so drop both
@ggorman ggorman changed the title compiler: Fix three guard bugs exposed by buffering compiler: Fix two guard bugs exposed by buffering Oct 19, 2026
@ggorman
ggorman marked this pull request as ready for review October 19, 2026 13:32
@ggorman
ggorman requested a review from FabioLuporini October 19, 2026 13:32

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants