Skip to content

feat(Core): add loop termination checking via decreases measure annotations#581

Merged
shigoel merged 9 commits intomainfrom
shilpi/loop-termination
Mar 16, 2026
Merged

feat(Core): add loop termination checking via decreases measure annotations#581
shigoel merged 9 commits intomainfrom
shilpi/loop-termination

Conversation

@shigoel
Copy link
Contributor

@shigoel shigoel commented Mar 15, 2026

Introduces support for decreases clauses on while loops in the Core
language: concrete syntax, passive encoding, CFG visualization, and verification
condition generation.

Before this PR, Core's AST supported the provision of a measure for a loop,
but didn't expose it (no concrete syntax) and the analysis didn't check
loop termination.

  • Loop termination VCs (LoopElim.lean)
  • decreases concrete syntax (DDMTransform)
  • New generic abstractions (DL/Imperative layer): HasIntOrder, HasIdent,
    HasInit
  • CFG visualization of measures (StructuredToUnstructured.lean)
  • PrecondElim support (PrecondElim.lean)

Also added a format instance for Core's experssions that shadows the generic
LExpr formatter with Core.formatExprs.

Tests updated in Loops.lean

  • New measureFailExamplePgm: decreases n on a countUp loop (wrong
    measure); confirms measure_decrease_0 fails while other VCs pass.
  • gaussPgm and nestedPgm annotated with measures.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

shigoel and others added 7 commits March 13, 2026 11:19
Previously, the entry invariant assert was placed inside the `ite guard`
then-branch, so when the guard was false at loop entry (0-iteration path)
the invariant was never checked — a soundness bug.

Fix: emit assert(I) unconditionally before the ite, followed by assume(I)
to make I an explicit path condition on the 0-iteration path.

Also replaces the brief module comment with a detailed passive encoding
recipe documenting the two-phase (VC1/VC2) inductive invariant check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…otations

Introduces support for `decreases` clauses on `while` loops in the Core
language: concrete syntax, passive encoding, CFG visualization, and verification
condition generation.

- Loop termination VCs (LoopElim.lean)
- `decreases` concrete syntax (DDMTransform)
- New generic abstractions (DL/Imperative layer): `HasIntOrder`, `HasIdent`,
`HasInit`
- CFG visualization of measures (StructuredToUnstructured.lean)
- PrecondElim support (PrecondElim.lean)

Also added a format instance for Core's experssions that shadows the generic
`LExpr` formatter with `Core.formatExprs`.

## Tests updated in Loops.lean

- New `measureFailExamplePgm`: `decreases n` on a `countUp` loop (wrong
  measure); confirms `measure_decrease_0` fails while other VCs pass.
- `gaussPgm` and `nestedPgm` annotated with measures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shigoel shigoel requested a review from a team March 15, 2026 22:50
@shigoel shigoel enabled auto-merge March 15, 2026 22:51
@tautschnig
Copy link
Contributor

Can the Goto conversion code please also be updated to include translation of these decreases annotations into the same decreases annotations in GOTO?

@shigoel
Copy link
Contributor Author

shigoel commented Mar 15, 2026

Can the Goto conversion code please also be updated to include translation of these decreases annotations into the same decreases annotations in GOTO?

I thought that's already implemented here. Am I missing something?

Before this PR, Core's AST supported the provision of a measure for a loop, but didn't expose it (no concrete syntax) and didn't check loop termination. My understanding was that the Goto conversion was future-proof for the decreases clause, but LMK if I misunderstood.

@tautschnig
Copy link
Contributor

Can the Goto conversion code please also be updated to include translation of these decreases annotations into the same decreases annotations in GOTO?

I thought that's already implemented here. Am I missing something?

Before this PR, Core's AST supported the provision of a measure for a loop, but didn't expose it (no concrete syntax) and didn't check loop termination. My understanding was that the Goto conversion was future-proof for the decreases clause, but LMK if I misunderstood.

My apologies, I indeed thought I had taken care of this, but then misunderstood the PR description as a new AST node. All good, no need for any Goto work then.

shigoel and others added 2 commits March 16, 2026 10:50
… decreases clause

Demonstrates that when a `decreases` measure uses integer division (`i / d`,
which compiles to `Int.SafeDiv`), the verifier generates a division-by-zero
precondition check for the measure expression, discharged by `requires (d > 0)`.
Also adds a negative example showing the failures when the precondition is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, precondition assertions for partial functions in a `decreases`
measure were only inserted before the loop.  If the loop body mutates a
variable used in the measure expression (e.g. `while e decreases y / x`
with `x := ...` in the body), the precondition of the measure had to be
re-checked after each iteration but wasn't, leaving a gap in verification.

Apply the same treatment as guard preconditions: emit `loop_measure_end`
assertions at the end of the loop body in addition to `loop_measure`
assertions before the loop.  Add a test (`precondElimMeasureBodyMutatesPgm`)
that demonstrates the failure when the divisor is decremented in the body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shigoel shigoel added this pull request to the merge queue Mar 16, 2026
Merged via the queue into main with commit 6b4f049 Mar 16, 2026
15 checks passed
@shigoel shigoel deleted the shilpi/loop-termination branch March 16, 2026 17:06
@joscoh joscoh mentioned this pull request Mar 17, 2026
tautschnig added a commit that referenced this pull request Mar 19, 2026
The loop termination feature (PR #581) added HasIdent, HasFvar,
HasIntOrder, and HasNot constraints to stmtsToCFG, but the
CFGToCProverGOTO test was not updated with the required instances.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
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.

4 participants