Problem
The optimizer's specialize+dce fixpoint converges on a constant chain one layer per round: a case-of-known-constructor fold exposes the next scrutinee only after the sweep that produced it finishes, so a bind-style chain of depth N needs on the order of N rounds to fold to its final value. This was invisible while nothing drove full-chain collapse, but the default directive pack (#242, PR #327) does exactly that: the ~300-deep golden stress chains (Golden.LongApplyChain, Golden.LongBindFlipped, Golden.LongWriterBind) diverged at the historical 100-round backstop, still diverged at 400, and converged only under 1000 (measured by bisecting the cap). PR #327 raised maxFixpointIterations from 100 to 1000 as a stopgap, which keeps the checked runner honest but makes a genuine loop bug burn 1000 whole-module sweeps before reporting, and leaves compile time O(depth × module size) for deep chains.
Approach
Make the chain fold depth-independent within one round: when a rewrite fires, re-examine the rewritten node (and its new parent redex) instead of deferring to the next whole-module sweep — a local worklist inside the bottom-up traversal, bounded by the usual growth vetoes so termination arguments are unchanged. With rounds no longer proportional to chain depth, maxFixpointIterations can return to a small value that catches over-reporting passes quickly.
Prerequisites / Relations
Follow-up to #242 / PR #327 (which introduced the 1000-round stopgap and the goldens that exercise it). Independent of the directive machinery itself — the same round-scaling applies to any rewrite cascade that exposes one redex per fold.
Verification / Measurement
With the local worklist, the three ~300-deep stress goldens converge within a small fixed round count (assert by lowering the cap in the checked runner used by the test suite); all goldens byte-identical to the current accepted state; wall-clock of the golden suite drops measurably (the Long* modules dominate it today).
Problem
The optimizer's
specialize+dcefixpoint converges on a constant chain one layer per round: a case-of-known-constructor fold exposes the next scrutinee only after the sweep that produced it finishes, so abind-style chain of depth N needs on the order of N rounds to fold to its final value. This was invisible while nothing drove full-chain collapse, but the default directive pack (#242, PR #327) does exactly that: the ~300-deep golden stress chains (Golden.LongApplyChain,Golden.LongBindFlipped,Golden.LongWriterBind) diverged at the historical 100-round backstop, still diverged at 400, and converged only under 1000 (measured by bisecting the cap). PR #327 raisedmaxFixpointIterationsfrom 100 to 1000 as a stopgap, which keeps the checked runner honest but makes a genuine loop bug burn 1000 whole-module sweeps before reporting, and leaves compile time O(depth × module size) for deep chains.Approach
Make the chain fold depth-independent within one round: when a rewrite fires, re-examine the rewritten node (and its new parent redex) instead of deferring to the next whole-module sweep — a local worklist inside the bottom-up traversal, bounded by the usual growth vetoes so termination arguments are unchanged. With rounds no longer proportional to chain depth,
maxFixpointIterationscan return to a small value that catches over-reporting passes quickly.Prerequisites / Relations
Follow-up to #242 / PR #327 (which introduced the 1000-round stopgap and the goldens that exercise it). Independent of the directive machinery itself — the same round-scaling applies to any rewrite cascade that exposes one redex per fold.
Verification / Measurement
With the local worklist, the three ~300-deep stress goldens converge within a small fixed round count (assert by lowering the cap in the checked runner used by the test suite); all goldens byte-identical to the current accepted state; wall-clock of the golden suite drops measurably (the Long* modules dominate it today).