Skip to content

Fix HUGR stall when lazy measurement values cross CFG branch transitions (#467) - #468

Merged
ciaranra merged 3 commits into
hugr-call-deferred-stallfrom
hugr-branch-carry-stall
Aug 10, 2026
Merged

Fix HUGR stall when lazy measurement values cross CFG branch transitions (#467)#468
ciaranra merged 3 commits into
hugr-call-deferred-stallfrom
hugr-branch-carry-stall

Conversation

@ciaranra

@ciaranra ciaranra commented Aug 9, 2026

Copy link
Copy Markdown
Member

Summary

Closes #467. A lazy measurement outcome that crossed a CFG branch transition before arriving
stalled execution -- the branch-transition sibling of #466's Call-boundary fix, found by that
PR's independent review pass and confirmed pre-existing on dev (all four dev/branch x
call/no-call combinations stalled identically).

Stacked on #466 (hugr-call-deferred-stall): both touch the replay machinery. Merge #466
first; this PR then retargets dev.

Root cause (trace-confirmed)

Cross-block measurement replay kept only each CFG's newest transition cascade. But cascade IDs
count invocations of the transition routine, not value generations -- consecutive live blocks
get increasing IDs just like loop iterations do. An outcome that had to walk an earlier edge
before the newest one found its intermediate port never filled, and the consumer starved.

The generation distinction the filter was trying to enforce already exists in the data:
re-entering a block purges prior edges into that target at recording time. Superseded loop
edges are gone by construction; live acyclic edges remain.

The fix

Delete the newest-cascade filter; replay every retained edge of active CFGs in insertion
order, fill-only. The purge-at-recording invariant is now documented where the filter used to
be. No special cases, no retry counters; the stall detector is untouched.

Also answered en route: plain measure never hit this because it ends the dispatch batch at
emission, while measure_and_reset records a late output wire and lets dispatch run ahead
through subsequent blocks -- the difference is batch scheduling, not measurement semantics.

Tests

Five value-asserting regressions (results checked, not merely absence of stall): no-call
branch carry, called variant, measurement-dependent dynamic branch, two consecutive branch
crossings, and a TailLoop that re-measures per iteration asserting the consumer sees the LAST
generation's value -- the case that would fail if replay-all resurrected stale generations.
Mutation (filter restored): exactly the five new tests fail; the same-block and Call-boundary
controls pass.

Verification

clippy -D warnings clean; fmt clean; pecos-hugr 85 + 6 doctests; guppy 496; pecos 2130;
qec 1147; doc blocks 9/10 (the #427 acceptance) still pass; pre-commit two passes exit 0.
#466's four regressions and its minimal repro all green on this branch.

@ciaranra

Copy link
Copy Markdown
Member Author

Independent review pass complete on this PR's delta (the replay-selection change, reviewed
against its base branch so the underlying Call-return work was not re-covered).

No findings. The pass targeted the one failure mode the fix's tests could not rule out by
construction: a retained-but-stale edge replaying a wrong value silently (a worse class than the
stall being fixed, since fill-only replay would make it invisible). Nine control-flow shapes were
executed with exact per-shot expected values derived from a fixed seed, 50 shots, each run
twice for determinism:

three-level nested conditionals (both directions exercised) / measurement-dependent loop count /
measurement consumed after a whole loop / branch inside a loop body / loop exiting on first
iteration / two sequential loops / measured value returned from a call inside a branch inside a
loop / two active functions with interleaved measurements / fill-only precedence (a normally
delivered value is never overwritten by a recorded edge).

All computed results matched their hand-derived expectations exactly; all runs deterministic;
the PR's five regressions plus the Call-boundary controls pass.

With this, the stack is review-complete: this PR merges into hugr-call-deferred-stall, and
#466 then delivers both fixes to dev (closing #427 and #467).

@ciaranra
ciaranra merged commit d525b26 into hugr-call-deferred-stall Aug 10, 2026
2 checks passed
@ciaranra
ciaranra deleted the hugr-branch-carry-stall branch August 10, 2026 05:11
ciaranra added a commit that referenced this pull request Aug 10, 2026
* Defer Call return publication until measurement outcomes resolve, fixing the HUGR stall on measured values crossing Call boundaries

* Replay all retained CFG transition edges so lazy measurement values survive branch crossings (#468)
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.

1 participant