Skip to content

Widen on the local iteration count, as the C++ iterator does - #33

Open
arthaud wants to merge 1 commit into
facebook:mainfrom
arthaud:fix-extrapolate-local-iterations
Open

Widen on the local iteration count, as the C++ iterator does#33
arthaud wants to merge 1 commit into
facebook:mainfrom
arthaud:fix-extrapolate-local-iterations

Conversation

@arthaud

@arthaud arthaud commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

MonotonicFixpointIterator::extrapolate is documented to apply the join at the first iteration and the widening operator at every subsequent one, which is what MonotonicFixpointIteratorBase::extrapolate does in the C++ implementation:

if (context.get_local_iterations_for(node) == 0) {
  current_state->join_with(new_state);
} else {
  current_state->widen_with(new_state);
}

It read the global iteration count instead, which is never reset. run already calls reset_local_iteration_count_for whenever a component stabilizes, exactly as the C++ iterator does, but nothing read the count back, so a component that stabilized and was then entered again with a larger state was widened right away rather than joined once more.

The result is still a sound post-fixpoint, but it loses precision on nested loops, where the inner component is re-entered every time the outer one grows.

The new test covers this: its inner component stabilizes once before the outer one grows x, so its head is extrapolated with a local iteration count of 0 and a global one of 1. It fails before this change, with z widened to top instead of holding {0, 5}.

`MonotonicFixpointIterator::extrapolate` is documented to apply the join
at the first iteration and the widening operator at every subsequent one,
which is what `MonotonicFixpointIteratorBase::extrapolate` does in the
C++ implementation:

    if (context.get_local_iterations_for(node) == 0) {
      current_state->join_with(new_state);
    } else {
      current_state->widen_with(new_state);
    }

It read the global iteration count instead, which is never reset. `run`
already calls `reset_local_iteration_count_for` whenever a component
stabilizes, exactly as the C++ iterator does, but nothing read the count
back, so a component that stabilized and was then entered again with a
larger state was widened right away rather than joined once more.

The result is still a sound post-fixpoint, but it loses precision on
nested loops, where the inner component is re-entered every time the
outer one grows.

The new test covers this: its inner component stabilizes once before the
outer one grows `x`, so its head is extrapolated with a local iteration
count of 0 and a global one of 1. It fails before this change, with `z`
widened to top instead of holding {0, 5}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGo5pDipqSuWEHtiwYmxTx
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant