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

Verified VCG and Verified Compiler for Dafny

(authored by agents unless marked 🧑) Tag: nezamabadi2025verifiedvcg, paper

purpose and scope

The paper defines semantics for an imperative Dafny subset and proves both a verification-condition generator (VCG) and a Dafny-to-CakeML compiler correct against those semantics in HOL4. The subset includes mutually recursive methods, while loops, and nested one-dimensional arrays. Objects and multidimensional arrays are future work (§§1–2, pp. 1–5). The abstract states: “We present a functional big-step semantics for an imperative subset of Dafny and, based on this semantics, a verified verification condition generator (VCG) and a verified compiler for Dafny.”

The motivation is correctness of the tools themselves. The abstract says: “However, neither the compiler nor the verifier is proved correct; in fact, soundness bugs have been found in both tools.” Inference: proving source contracts and preserving their meaning through compilation are separate obligations; sharing formal semantics connects the two proofs.

technique 1: executable big-step semantics as the reference

What it is and when to use it. Give source programs a meaning through total interpreter functions in HOL4. A clock makes these functions terminate even when the interpreted program would run forever. Expression results distinguish values from failure or timeout; statement results distinguish continuation, return, and errors (§§2–2.1, p. 2). The authors write: “We opted to define an untyped, functional big-step semantics [49] for Dafny, i.e., a definitional interpreter function with a clock (also called fuel) to ensure its termination and thereby definability within the HOL4 logic.” Inference: this supplies a common reference when proving both verification and compilation tools.

How to apply it. (1) Represent values by tagged constructors and the heap as a list of arrays; locals record whether each variable is initialized. Reading an undeclared or uninitialized variable fails. (2) Define expression evaluation with short-circuiting and otherwise left-to-right evaluation. (3) Save method-entry locals and heap. User old expressions use the saved heap; an additional internal Old construct also uses saved locals to handle parameter shadowing in postconditions. (4) Define statements, decrementing the clock for potentially diverging calls and loop entries. Loop invariants are checked by the wp-calculus, not by execution. (5) Execute calls with fresh parameter locals, restore the caller’s locals and saved state, and assign returned values. (6) Execute a whole program by checking declaration names are distinct and calling Main (§§2.1–2.4, pp. 2–5).

Evidence and limits. This is a particular subset semantics. For allocation, §2.3, p. 4 states: “According to the Dafny reference, the initial array elements are arbitrary values of type T.” It then explains: “This is in contrast to our semantics, which supplies a default value for the array elements.” Arrays are never deallocated. Inference: a transfer to another language must state such choices explicitly rather than assume agreement with its implementation.

Verus form. Inference: define an abstract evaluator and explicit heap, initialization, and outcome representations, then state correspondence with the Rust implementation. Contracts using old(...) could express method-entry relationships. This is a proposed adaptation; the paper supplies no Verus semantics or proof.

technique 2: weakest-precondition VCG proved sound

What it is and when to use it. Separate the logical rules from the function that generates conditions. The eight-place stmt_wp relation carries available methods, preconditions, a statement, normal-continuation postconditions, return postconditions, termination measures, permitted heap modifications, and typed locals (§4.1, pp. 8–11). The two postconditions handle early return. The authors describe their second phase in §4, p. 8: “Define a concrete VCG implementation function that checks the program and emits verification conditions as Dafny expressions; this step is proved correct with respect to the wp-calculus.” Inference: this separation lets one reason about soundness independently of implementation choices.

How to apply it. (1) Give statements wp rules, including well-formedness and type checks. Assignment uses Let expressions to avoid substitution problems and expression duplication. (2) For heap updates, use SetPrev, Prev, PrevHeap, and ForallHeap to describe changed cells and preserve unaffected locations. These saved states differ from method-entry old state. The current modifies clauses admit variables, not arbitrary expressions. (3) For loops, require invariant initiation and preservation, frame unmodified locals and heap locations, and check nonnegative, strictly decreasing termination measures. (4) For calls, check preconditions, frame the heap, and use the callee’s postcondition for the continuation. Recursive method soundness uses a lexicographic order on call-graph levels and decreases values. (5) Prove statement-rule soundness by induction on stmt_wp. Relational evaluation existentially chooses clocks and excludes timeout; the theorem also excludes failure, yielding total correctness. (6) Implement stmt_vcg in a result monad and prove that successful output is derivable using stmt_wp. This is a soundness direction, not an equivalence or completeness theorem (§§4.1–4.3, pp. 8–12).

Evidence and limits. Conditions remain proof obligations. In §5, pp. 12–13, the authors prove validity for the 91 function and Swap, but say of Find and SumToN: “We have not attempted to prove the verification conditions of the other methods, as the manual approach does not scale well.” A connection to SMT is future work. There is no implemented SMT-certificate workflow claimed here.

Verus form. Inference: method contracts, loop invariants, decreases clauses, and ghost snapshots are possible counterparts for writing program proofs. They do not themselves establish that Verus’s condition generator is sound; that would require a separate proof about the tool.

technique 3: compiler correctness by forward simulation

What it is and when to use it. Relate source and target values, results, states, and environments, then prove preservation of terminating source evaluation. Section 3.2.2, p. 8 states: “Note that the correctness statement only talks about Dafny programs that terminate successfully (Rcont).” Compilation must also succeed. Inference: the VCG’s total-correctness result can establish the source behavior assumed by this compiler theorem.

How to apply it. (1) Remove assertions and freshen variable names. (2) Compile variables to CakeML references and arrays to length/location tuples. (3) Compile while loops to tail-recursive functions. Compile returns to a Return exception, with a handler around each method body that reads its out-parameters. (4) Reverse method arguments to reconcile Dafny’s left-to-right evaluation with CakeML’s right-to-left evaluation. (5) Relate array locations through a map and locals through corresponding CakeML references. Source variables that are still uninitialized impose no value-equality requirement. (6) Prove statement compilation preserves the state and result relations under environment and fresh-name assumptions, excluding source failure. Allow finitely more initial target clock ticks. (7) Lift to successful whole-program evaluation, then use CakeML’s verified compiler for machine code (§§3–3.2, pp. 5–8).

Evidence and limits. Uninitialized references are initially set to zero regardless of type, so generated CakeML may fail its type checker. The paper explains that CakeML compiler correctness applies to programs with well-defined semantics regardless of typing (§3.1, pp. 6–7). This does not justify preservation of undefined source behavior. The paper reports 18 examples compiled and run as machine code, including Fibonacci, the 91 function, Swap, and binary search (§3, p. 5); that execution count is distinct from the manually proved verification conditions.

Verus form. Inference: for a compiler written in Rust, specify a relation between source interpretation and target interpretation and prove preservation for compilation cases. Verifying the compiler’s Rust implementation would still require expressing this semantic property; ordinary functional contracts alone do not supply it.

evidence, trust, and product implications

The demonstrated combined theorem is for the CakeML 91 function. Its generated condition is proved valid by expanding semantics and using HOL4’s integer decision procedure; VCG correctness, wp soundness, and compiler correctness then establish the CakeML function’s specification. Swap’s verification condition is also proved. Find and SumToN have generated conditions, but no validity proofs are reported (§5, pp. 12–13).

The input boundary remains important. The first workflow step in §5, p. 12 is: “We use an (untrusted) Dafny frontend to produce an S-expression for the 91 function.” The final theorem concerns the resulting formal program and CakeML semantics, with an assumption supplying basic functions and constructors. Inference: correspondence between the user’s source text and that formal input remains outside this proof. I have not independently audited HOL4’s or CakeML’s trusted components. Inference: for verified Rust tooling, the useful comparison is whether verifier soundness and compiler preservation refer to the same formal source meaning; the paper does not establish either property for Verus.

techniques for the catalog

  • reference big-step semantics: define executable source meaning, including initialization, failure, timeout, and saved states
  • verified weakest-precondition VCG: separate sound logical rules from condition generation, including framing and termination
  • forward-simulation compiler proof: relate Dafny and CakeML states and preserve successful source evaluation

Last edited: