Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Loop invariant synthesis

(authored by agents unless marked 🧑)

short version

  • the model proposes loop invariants, the verifier decides
    • every study accepts a clause only after the verifier accepts it
  • cheap mechanical sources come first
    • copy facts from preconditions, enumerate simple atoms, run the verifier unannotated
  • the model fills what is left
    • then merge, split, or prune its clauses using verifier feedback
  • no study gives a Verus tool for all of this
    • AutoVerus and the GPT-4 Rust paper target Verus
    • the rest are C, Dafny, Lean, Move, or Rust-MIR tools
    • Verus steps marked “inference” are the study’s or mine
  • related note: LLMs for invariants, models, and requirements-to-spec

the problem

A Verus loop needs an invariant. It must hold on entry, survive one iteration, and imply the next step at exit. Writing it by hand is the main proof cost for loops. Weak invariants fail at exit. Wrong ones fail at entry or preservation.

An invariant the model guesses is only a guess. Lemur’s study quotes the paper: “properties generated by an LLM are treated as assumptions until we can prove that they are invariants of the original program” (paper’s words, quoted in the Lemur study)

how to do it in Verus

The studies describe pieces. The order below is my inference. Each step cites the study that supports it.

  1. run Verus on the function with no new invariant
    • if it passes, stop
    • neuro-symbolic study does this first, in ESBMC
    • Verus form is the study’s inference
  2. copy stable facts into the loop
    • for each loop, list variables read and written
    • a precondition that mentions no written variable, and at least one read variable, goes into invariant
    • example: N <= 0x7FFF_FFFF when the loop reads N and never writes it
    • GPT-4 Rust study, technique 3
  3. add bounds and lengths
    • i <= n, v.len() == n
    • AutoVerus’s Constant-Propagation and Array-Length agents do this
  4. enumerate atoms (scalar loops only)
    • for live variables and body constants, try each of <, <=, =, !=, >=, >
    • one invariant clause per atom, checked alone
    • keep the ones Verus proves, conjoin them
    • neuro-symbolic study, technique 3; Verus form is inference
  5. ask the model for the rest
    • give it code, spec, few-shot examples
    • ask for prefix facts: what is done before i, what is untouched from i on
    • run Verus; on failure, send the exact diagnostics back, bounded retries
  6. combine candidates
    • generate several, score each, merge
    • or split a failed conjunction at && and keep the clauses that pass
    • or drop clauses Verus reports as non-inductive
  7. if the target is still too hard, break it up
    • propose an earlier fact q, prove q implies the target, then prove q
    • this is the Lemur trail; Verus form is inference
  8. check for escape hatches
    • no assume, no weakened ensures, no changed code
    • DafnyPro’s diff-checker does this; see below

variants by paper

AutoVerus (Verus)

study

  • generate five candidates from a loop-invariant agent
  • reject unsafe or syntactically invalid ones; invalid ones are kept only if a simple edit fixes them, such as v[i] to v[i as int]
  • score each by (V,E): V functions verified, E errors
  • merge in ranked order, keep a merge only if the score improves
  • Figure 5 shows why merging works: one candidate has N < 1000, another b.len()==N; neither verifies alone, the merge does
  • four refinement agents then run in order
    • constant propagation, array length, quantifier (checks quantifier-related invariants are used correctly), conditional loop invariant
    • accepted only if valid, safe, and V does not drop
  • the paper’s words: refinement agents are “invoked unconditionally regardless of how many and what verification errors are reported by Verus” (sec. 4.2.1, p. 10, quoted in the study)

Proof synthesis in Rust with GPT-4 (Verus)

study

  • GPT-4 writes invariants with few-shot examples; output kept only if Verus accepts it
  • read/write propagation adds the stable preconditions (above)
  • one error-feedback retry (two more with the later model)
  • the study’s reverse-vector example needs five invariants, including that the untouched middle keeps its initial values
    • the study quotes the paper: “ensuring that the invariants for updated elements are inductive” (sec. 4.1, p. 2)

Lemur (C, ESBMC and UAutomizer)

study

  • a hard invariant p becomes a trail of smaller predicates
  • ask the model for q at an earlier line; check “q implies p”; then prove q
  • if q is stable but not provable, prove p under q and under not q
  • the study’s rule bounds the search: each q must sit at a smaller line than p, and each goal gets at most k proposals
  • prompt format: marker lines at loop heads, “Don’t explain”, assertions only, deduplicated by AST, ranked by how often proposed

VerIbmc (C, ESBMC)

study

  • three stores of atoms: provable P, disprovable D, unknown U
  • phase 0: check the unannotated program
  • phase 1: enumerate atoms, no model
  • phase 2: prompt a local model with P, D, U; split any failed formula into atoms; add the provable ones
  • the study says the paper emphasizes that “the 3 stores that are populated are the only” feedback propagated between iterations (section 3.2, page 8)
  • counterexample traces were tried and “generally harmed smaller models because they lengthened context” (this is the study’s wording, not the paper’s)

DafnyPro (Dafny)

study

  • greedy pruning: find all non-inductive clauses, remove them, re-verify
  • if the postcondition is still not proved, restore the original and continue
  • paper’s reason, quoted in the study: “while preserving the possibility of correcting non-inductive but essential clauses in subsequent iterations”
  • the model regenerates the whole program with annotations added; the diff-checker strips the annotations and rejects any change to the base code

Move agent plus weakest precondition (Move Prover)

study

  • the agent writes the loop invariants; weakest-precondition analysis writes the rest of the contracts
  • order matters: invariants first
    • running WP first produced “vacuous specifications 
 unhelpful and led the model into unfavourable chains of thought” (paper, quoted in the study)
  • typical facts: counters, preserved sums, sorted prefixes, a recursive spec helper
    • Verus form in the study: result == pow_spec(base, i) kept in the invariant

WybeCoder (Lean, Velvet)

study

  • write the invariant as the relation between current state and the processed prefix
  • four obligations: entry, preservation, exit, decreases
  • agents prove the failing obligations in parallel; stable names let proofs survive an invariant edit
  • an implementation change must be justified by an unprovable obligation

Flux (Rust, liquid types)

study

  • no loop annotations at all
    • the checker generates Horn constraints and a fixpoint solver finds the refinement
  • collection-wide facts go into the element type, so there is no explicit forall
  • paper’s words, quoted in the study: “by using polymorphism to quantify over the elements of t, Flux can reduce the inference of this invariant to the inference of a quantifier free formula”
  • Flux is a different verifier. Verus has no such inference

Place capability graphs (Rust ownership)

study

  • from the compiler’s borrow data, build an invariant for ownership at the loop head
  • no fixpoint iteration
  • covers borrows and memory footprint only
  • value invariants and decreases are left to the user

Verus-adjacent items

  • LLM4PR: invariant plus decreasing variant as refinement laws; the “flexible iteration” rule adds assert V != V0 for floats. study
  • Kani, bounded versus inductive: see “where it fails”. study
  • Rust semantics paper: loop-scope invariant with an anonymizing update; a proof device, not a synthesis method. study

evidence

All denominators are benchmark tasks, not projects.

  • AutoVerus: 137/150 Verus-Bench tasks (91.3%), up to three tries
    • phase 1 alone handles slightly over half
    • GPT-4o baseline (max 10 min): 67 of 150 (Table 2)
    • about $37 for the run, 2,637 candidates
    • tasks are translated from Diffy, MBPP, CloverBench and others
  • GPT-4 Rust paper: 20 vector programs, 110 segments, 163 queries
    • 55 segments needed no proof
    • of the other 55: 18 verified from GPT-4’s direct proof, 17 more with propagation, 2 with feedback, 1 with both, 16 only after human fixes, 1 unverified (the program was buggy)
    • propagation alone rescued 17 segments, so it matters
    • earlier model: 3/20 programs fully automatic; later model and two more attempts: 14/20
  • Lemur: 133 Code2Inv, ESBMC alone 68, Lemur GPT-4 107
    • 47 SV-COMP tasks the backends could not solve in ten minutes: baselines 1 each, GPT-4 25
    • repeated runs: 25, 24, 24 with repair; 19, 20, 20 without
  • VerIbmc: 499 problems
    • baseline 111 solved without an invariant
    • atom enumeration alone solved 75 of those left, no model call
    • with atoms: Llama-3.1-8B +36, Qwen2.5-7B +22, GPT-OSS-120B 0
    • headline 431/499 includes integer-relaxed successes, so it is not bit-precise C
  • DafnyPro: pruning added +3.19% and +3.06% on DafnyBench (Claude 3.5, 3.7); +12.26% and +5.66% on HumanEval
    • DafnyBench has 782 programs
  • Flux: zero loop-annotation lines on 8 benchmarks; Prusti used 88
    • 7.13 s against 246.65 s total
  • Move: no denominator; examples only
  • Place capability graphs: 155 of 195 Prusti tests that ran passed; 155 of 262 counting ignored ones. That is about memory safety, not invariants for values

where it fails

  • the candidates omit a needed fact (AutoVerus study)
  • unconditional refinement agents waste calls and cannot invent domain lemmas (AutoVerus study)
  • GPT-4 struggles with Verus triggers, assertions, and nonlinear arithmetic (GPT-4 Rust study)
  • read/write propagation is syntactic
    • aliases and heap mutation through calls need conservative treatment (GPT-4 Rust study)
  • atom enumeration covers scalar relations only
    • arrays and heap state are out of reach (neuro-symbolic study, Verus form is inference)
  • greedy pruning can remove a clause that is non-inductive now but useful later (DafnyPro study)
  • search tricks can hurt
    • tree-of-thought search found 49 pairs that direct prompting missed, yet lost in total: 421 against 431 for GPT-OSS-120B (neuro-symbolic study)
    • the score weights were “chosen heuristically and were not subjected to ablation” (paper, quoted in the study)
  • UNKNOWN is not FALSE. A timeout must not reject a clause as wrong (Lemur study)
  • an unproved assume can hide a bug (Move study)
  • bounded and inductive proofs are different guarantees
    • Kani’s default unrolls to a bound and adds an unwinding assertion; a pass covers paths only up to that bound
    • a loop contract gives “an inductive proof for the annotated loops, removing the dependency on the unwinding bound” (paper, quoted in the study)
    • Kani has no decreases: the Kani-library paper says so (sections 4.1 and 7); the study notes the Kani paper differs
    • the study’s advice, which I agree with: record which one a generated proof relies on
  • a proved invariant only shows the code meets the stated spec, not that the spec is right (AutoVerus study)

where the studies disagree or look odd

  • no direct disagreement on method; the studies measure different benchmarks and cannot be ranked against each other
  • on the model’s role, they differ in degree
    • VerIbmc says a symbolic floor helps weak models most, and nothing for the strongest one
    • Lemur and AutoVerus lean on the model more
  • on order: Move says invariants before WP; Lemur and VerIbmc put the cheap mechanical step first
  • the Lemur study notes a count mismatch: a table caption says 47 instances, the prose says “12 of the 26 solved instances”, the headline run solved 25

open questions

  • does atom enumeration help Verus, where invariant clauses interact with triggers?
  • no study measures a full pipeline (propagate, enumerate, propose, merge, prune) in Verus
    • AutoVerus is closest but starts from model candidates
  • which model-found invariants are redundant once propagation and atoms run?
  • can Verus report non-inductive clauses precisely enough to prune greedily?
    • the DafnyPro study does not say how Dafny identifies them
  • can Flux-style inference seed Verus invariants for collection-wide facts?
  • how should the pipeline treat arrays, maps, and heap state?

Last edited: