feat(analyzer): diagnose modification of for-loop bounds - #3135
Draft
tignear wants to merge 1 commit into
Draft
Conversation
tignear
force-pushed
the
fix/reevaluate-dynamic-for-bound
branch
2 times, most recently
from
August 3, 2026 11:45
681d938 to
94ab52e
Compare
tignear
force-pushed
the
fix/reevaluate-dynamic-for-bound
branch
from
August 3, 2026 11:59
94ab52e to
d3147fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status and request for feedback
I started this PR with the expectation that checking the continuation-bound reads against writes in the loop body would be a reasonably local analyzer change.
While implementing and reviewing it, the problem turned out to be substantially larger:
The current patch detects a useful subset of problematic loops, but it does not appear to solve the underlying problem, and extending it further in its current form risks duplicating or further coupling several existing dependency collectors.
I am therefore not attached to merging this implementation, or even to keeping this exact diagnostic design. I opened the PR because the local check initially seemed like a reasonable first step, but the investigation has made it much less clear that this is the right architectural direction.
If anyone has a cleaner language rule, dependency-analysis model, migration strategy, or entirely different approach, please comment. I would be happy for this PR to be replaced or closed if it helps converge on a more complete solution.
Summary
Add the
mutable_for_boundwarning when the analyzer can establish that a proceduralforloop body immediately writes storage read by its continuation bound.Assignments unrelated to the continuation bound remain valid. Writes with true FF/NBA semantics are also permitted because their updated values are not visible until after the current procedural execution completes.
Procedural locals inside
always_ffstill use immediate assignment semantics and are diagnosed when modified.The warning can be suppressed during migration:
This PR intentionally implements only the immediate procedural-write check. It does not claim that every indirect or cross-hierarchy influence is currently diagnosed.
Motivation
Veryl currently does not document when a procedural
forloop continuation bound is evaluated.The native simulator captures the bound before entering the loop, while emitted SystemVerilog evaluates the continuation condition before each iteration. Modifying a bound from the loop body can therefore change the iteration count or termination behavior depending on the backend.
Rather than choosing one evaluation strategy immediately, Veryl can preserve implementation freedom by diagnosing programs that can observe the difference.
For synthesizable code, the intended argument is:
Under a sound combinational DAG restriction, rejecting immediate procedural mutation is therefore sufficient to make capture-on-entry and per-iteration evaluation equivalent for ordinary synthesizable code.
This PR addresses the immediate-mutation part of that argument.
Why this is initially a warning
The direct check implemented here is deliberately conservative but incomplete.
It currently handles:
It does not construct the complete combinational dependency relation of the elaborated design. In particular, this checker does not independently resolve:
Some indirect cases should already be rejected by Veryl's combinational-loop rule. However, the current combinational-loop analyzer also contains deliberate under-detection paths. Among other cases, it may omit dependencies involving SystemVerilog black boxes,
inoutports, oversized arrays, or modules whose assignment analysis was suppressed.Consequently, an indirect feedback path that is invalid according to the language-level DAG rule may still be missed by the current implementation. This PR plus the existing DAG analyzer therefore does not yet guarantee that every warning-free program is independent of the bound-evaluation strategy.
Making the new diagnostic a warning exposes the known immediate cases without presenting the surrounding dependency analysis as complete or immediately rejecting existing programs.
Intended diagnostic model
The eventual analysis should distinguish three outcomes:
Improving analysis precision may refine a warning into either an error or no diagnostic. An analysis limitation must not be represented by silently dropping dependency edges and treating the result as proof of stability.
This does not require a flat whole-design graph. A future implementation can use total module-local dependency graphs and compositional module summaries which preserve an explicit incomplete or unknown result.
The warning introduced by this PR is a first migration step toward that model.
Testbench code
The warning also applies to known immediate mutation in testbench loops because such code would directly observe the same evaluation-strategy choice.
The synthesizable-code DAG argument does not extend to time-advancing testbench bodies. A testbench may yield, update FF state, or observe changed DUT outputs before the next continuation test without creating a combinational cycle.
This PR does not attempt that event-mediated analysis. Such cases may require a separate conservative warning based on event and write closures.
Alternatives considered
Re-evaluate the bound in the native simulator
The simulator could match emitted SystemVerilog by evaluating the continuation bound before every iteration.
This would define useful behavior for a fragile pattern and would impose repeated evaluation in the common case unless each simulator backend performed loop-invariance analysis.
Capture the bound in emitted SystemVerilog
The emitter could introduce a temporary which captures the continuation bound before entering the loop.
This would match the current native simulator but make emitted SystemVerilog more verbose and commit Veryl to capture-on-entry semantics.
Specify one-time range evaluation
Rust, whose range syntax Veryl resembles, evaluates the range expression before iteration.
Veryl currently has no documented semantics under which mutating a continuation bound is useful. Choosing this model would constrain future backends even though well-formed programs need not observe the choice.
Expand this PR into a complete dependency-analysis rewrite
A complete solution could replace the current overlapping dependency collectors with a total, sound, conservative dependency representation shared by combinational-loop detection and continuation-bound analysis.
That is substantially larger than this diagnostic and should be handled separately. This PR keeps the directly observable procedural case visible while that design is considered.
Compatibility and future work
Programs diagnosed by this PR remain accepted and produce a warning. The warning may be suppressed with
#[allow(mutable_for_bound)].Known immediate mutation can be promoted to an error after the language rule and migration policy are agreed. Analysis-incomplete cases should remain warnings until they can be resolved conservatively.
A warning-free-program guarantee additionally requires the combinational dependency analysis to stop silently omitting relevant edges. That work is not part of this PR.