Skip to content

refactor: rework the vcgen frame procedure API around a commit callback - #14706

Draft
sgraf812 wants to merge 6 commits into
masterfrom
sg/vcgen-seplogic-next
Draft

refactor: rework the vcgen frame procedure API around a commit callback#14706
sgraf812 wants to merge 6 commits into
masterfrom
sg/vcgen-seplogic-next

Conversation

@sgraf812

@sgraf812 sgraf812 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This PR replaces the vcgen frame procedure protocol. A procedure now receives a commit callback that applies the frame rule and the spec rule eagerly and hands back the live subgoals (the frame ?F, the split VC pre ⊑ (op ?F specPre) s⃗, the WP.Frames condition, and the spec's remaining goals); the procedure signals framing by calling it, in place of returning an Option FrameSplit that the solver replayed. Because the split VC carries the spec's real parameter metavariables, cancelling atoms against it pins spec parameters permanently, which the previous speculative-application protocol could not do (its specPre? reopened the metavariables as unrelated copies after rollback).

The frame rule is restated as WP.Frames.op_le_wp with the footprint P as a parameter, so commit needs no proof surgery: applying the spec rule to the residual premise ?P ⊑ W and fixing ?P as the spec's precondition leaves the split VC in its final shape; when the spec rule does not fit the premise (its excess-argument count differs), ?P is fixed as the weakest footprint W and the split VC re-enters solve, reproducing the deferred behavior of the meet and tick worlds. Procedures run in GrindM, return the subgoals they create, and FrameInferenceProc.ofFrame? recovers the plain frame-returning style for procedures that discharge nothing. FrameSplit, FrameBackwardRule, specPre?, mkResidualPre, and mkSplitVCS are deleted; peekSpecPre (a Pattern.match? instantiation, data only) informs the commit decision.

The separation-logic demo's procedure now cancels partially: paired atoms are cancelled out of the split VC (pinning the paired spec parameters), and unmatched footprint atoms survive into a residual VC rest ⊑ ?F ∗ unmatched with the frame left schematic, surfaced as a hole the user fills. The demo's hand-ramified append specification and the emp-frame machinery serving it are removed: the plain specification now reaches every call site through cancellation. New tests exercise the partial-cancellation flow against an abstract-predicate mismatch.

@sgraf812 sgraf812 added the changelog-no Do not include this PR in the release changelog label Aug 6, 2026
Add `IsArray`, whose cells are reached by address arithmetic rather than by following pointers, and verify a loop that sums one. `IsArray_focus` splits the block at an index and hands back a wand to close it again, so `load_array` reads a cell without the index arithmetic reaching the loop invariant.
This PR restates the separation-logic demo's `append` specification in plain Hoare form, with the postcondition stated directly instead of received through a wand from a schematic postcondition.

The loop invariant no longer carries a continuation wand, so the six entailment lemmas feeding it lose their `K` parameter. The frame procedure's branch for a precondition whose uncancelled atom is a wand, which existed to apply the ramified specification at `emp` frame, is removed along with its `emp`-framed trivial-wand lemmas: call sites now reach the specification through ordinary domain-difference cancellation.
This PR replaces the vcgen frame procedure protocol. A procedure now receives a `commit` callback that applies the frame rule and the spec rule eagerly and hands back the live subgoals (the frame `?F`, the split VC `pre ⊑ (op ?F specPre) s⃗`, the `WP.Frames` condition, and the spec's remaining goals); the procedure signals framing by calling it, in place of returning an `Option FrameSplit` that the solver replayed. Because the split VC carries the spec's real parameter metavariables, cancelling atoms against it pins spec parameters permanently, which the previous speculative-application protocol could not do (its `specPre?` reopened the metavariables as unrelated copies after rollback).

The frame rule is restated as `WP.Frames.op_le_wp` with the footprint `P` as a parameter, so `commit` needs no proof surgery: applying the spec rule to the residual premise `?P ⊑ W` and fixing `?P` as the spec's precondition leaves the split VC in its final shape; when the spec rule does not fit the premise (its excess-argument count differs), `?P` is fixed as the weakest footprint `W` and the split VC re-enters `solve`, reproducing the deferred behavior of the meet and tick worlds. Procedures run in `GrindM`, return the subgoals they create, and `FrameInferenceProc.ofFrame?` recovers the plain frame-returning style for procedures that discharge nothing. `FrameSplit`, `FrameBackwardRule`, `specPre?`, `mkResidualPre`, and `mkSplitVCS` are deleted; `peekSpecPre` (a `Pattern.match?` instantiation, data only) informs the commit decision.

The separation-logic demo's procedure now cancels partially: paired atoms are cancelled out of the split VC (pinning the paired spec parameters), and unmatched footprint atoms survive into a residual VC `rest ⊑ ?F ∗ unmatched` with the frame left schematic, surfaced as a hole the user fills. New tests exercise the partial-cancellation flow against an abstract-predicate mismatch.
This PR simplifies the frame commit plumbing. The solver detects a commit by the goal being assigned and collects the subgoals as the unassigned metavariables of the goal's proof term, so the `IO.Ref` channel, the `List MVarId` returned by every procedure, and the exception save/restore disappear; `FrameInferenceProc` returns `Unit` again. `commitFrameRule` closes the residual premise's precondition VC (or, without a spec application, the premise itself) through the existing `rfl?` rule, removing the hand-built reflexivity proofs, and `FrameGoals` shrinks to the three named handles `F`, `splitVC`, `frames`.
…lot indexing

This PR reverts the `WP.Frames.op_le_wp` frame rule introduced earlier in this branch and returns to `op_wp_upperAdjoint_le_wp`. Parameterizing the rule by the footprint `P` put the residual premise `P ⊑ wp x (fun a => upperAdjoint (op F) (Q a)) E` in the lemma's hypotheses rather than its conclusion, and `mkSpecBackwardProof` applies the goal's excess state arguments only to the conclusion and its precondition. The premise therefore sat at the unapplied assertion type while the split VC beside it carried `s⃗`, so the spec rule failed to apply to it whenever the assertion type was stateful, and `FrameGoals.splitVC` silently meant `op ?F specPre` with no excess arguments and `op ?F W` with them.

`FrameBackwardRule` and its cached slot positions come back, extended with the frame condition's position, so `commit` reads its three subgoals off by index instead of searching the applied rule's goals by shape. All `FrameGoals` fields are metavariables. `controlAt` replaces the hand-written `VCGenM`-to-`GrindM` reifier at the procedure call site.

A procedure that wants the spec's parameters live applies the spec rule to the footprint entailment itself, at the excess arguments its own operator split produced: the separation-logic demo does this in `openFootprint`, and the precondition VC it gets back carries the spec's own metavariables, so cancelling against it pins them. Cancelling a pinned frame leaves that precondition VC to the user, which is how the abstract-predicate test now folds the representation without any parameter holes reaching `finish`.
…ents

This PR adds a regression guard to the cost frame procedure. `vcgen` applies a spec at the goal's excess state arguments, so the footprint the frame rule leaves must be reachable there; the guard applies the spec rule to the footprint entailment and reports when it is not. Stating the footprint at the unapplied assertion type, which happens when a frame rule takes it as a hypothesis rather than in its conclusion, goes unnoticed by every test whose assertion type carries no state.
@sgraf812
sgraf812 force-pushed the sg/vcgen-seplogic-next branch from a4f04c3 to 8e423b4 Compare August 11, 2026 08:23
@github-actions github-actions Bot added the toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN label Aug 11, 2026
@mathlib-lean-pr-testing

Copy link
Copy Markdown

Mathlib CI status (docs):

  • ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the nightly-with-mathlib branch. Try git rebase e98a70f4a7fcce0d76f3ef032aef59534fa78431 --onto 3fc29d37a70f8fd904ebab848557c12383543008. You can force Mathlib CI using the force-mathlib-ci label. (2026-08-11 08:45:00)

@leanprover-bot

Copy link
Copy Markdown
Collaborator

Reference manual CI status:

  • ❗ Reference manual CI will not be attempted unless your PR branches off the nightly-with-manual branch. Try git rebase e98a70f4a7fcce0d76f3ef032aef59534fa78431 --onto 3fc29d37a70f8fd904ebab848557c12383543008. You can force reference manual CI using the force-manual-ci label. (2026-08-11 08:45:01)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog-no Do not include this PR in the release changelog toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants