VST-A: A Foundationally Sound Annotation Verifier

Program verifiers for imperative languages such as C may be annotation-based, in which assertions and invariants are put into source files and then checked, or tactic-based, where proof scripts separate from programs are interactively developed in a proof assistant such as Coq. Annotation verifiers have been more automated and convenient, but some interactive verifiers have richer assertion languages and formal proofs of soundness. We present VST-A, an annotation verifier that uses the rich assertion language of VST, leverages the formal soundness proof of VST, but allows users to describe functional correctness proofs intuitively by inserting assertions. VST-A analyzes control flow graphs, decomposes every C function into control flow paths between assertions, and reduces program verification problems into corresponding straightline Hoare triples. Compared to existing foundational program verification tools like VST and Iris, in VST-A such decompositions and reductions can nonstructural, which makes VST-A more flexible to use. VST-A's decomposition and reduction is defined in Coq, proved sound in Coq, and computed call-by-value in Coq. The soundness proof for reduction is totally logical, independent of the complicated semantic model (and soundness proof) of VST's Hoare triple. Because of the rich assertion language, not all reduced proof goals can be automatically checked, but the system allows users to prove residual proof goals using the full power of the Coq proof assistant.


INTRODUCTION
In the past 15 years, researchers have built several tools for program verification.These tools are used in different ways and have their own advantages.
Interactive program verification tools, such as Iris [Jung et al. 2018;Krebbers et al. 2017b] and VST [Appel 2011;Beringer 2021;Cao et al. 2018], are built in interactive theorem provers like Coq or HOL4.Users write formal program correctness proofs in the same theorem prover, by using the lemmas and tactics of the program verification tool.
Some of these interactive tools themselves are foundationally sound (i.e., have a formal proof w.r.t. the language's operational semantics in the proof assistant).This is especially meaningful for verifying real-world programs and higher-level properties such as functional correctness.First, real-world programming languages are complicated.For example, it is very subtle to determine what C programs may cause undefined behavior.Second, an advanced program logic for higher-order properties usually has a nontrivial soundness proof.For example, VST and Iris use step-indexed semantics to interpret impredicative assertion languages whose soundness proof is complicated.
Interactive program verification tools can also benefit from the rich logic language and the rich proof language of theorem provers (like Coq and HOL).These tools can easily shallow-embed higher-order functions and predicates in their assertion languages.They also make it convenient (in specifying programs and program assertions) to introduce additional logical connectives.Additionally, the tactic proof languages in proof assistants are very powerful when users need to describe proof strategies such as proof-by-induction and proof-by-contradiction.
Compared with writing tactical proofs in a theorem prover, writing annotations is a much more straightforward way of demonstrating that a program is correct.Even proofs in theory papers and proofs written completely in an interactive prover are often presented in research papers as annotated programs, e.g.Reynolds's first paper about separation logic uses annotations to describe separation logic proofs [Reynolds 2002, page 10], and recent verification papers like Jung et al. [2020]'s work of extending Iris to support prophercy variables also uses annotations to describe their proofs [Jung et al. 2020, page 7].Fig. 1 shows an implementation of an in-place linked list reversal and its functional correctness proof. 1 The annotations on lines 3-5 describe the specification this function should satisfy: for any list of integers  (the With clause on line 3), if  is stored in a linked list and this link list's head pointer is passed to the function by the program variable p (the Require clause on line 4), then the function reverses the linked list and returns the new head pointer (the Ensure clause on line 5).The assertion on line 9 describes the main idea of a functional correctness proof.It states the criteria that the program state should satisfy every time the program enters the loop body.Assertion-annotated programs can present proofs succinctly; by contrast, in interactive verifiers, key insights into program correctness easily get mixed with structural tactics and become lengthy proof scripts.
In this paper, we demonstrate how to combine the benefits of interactive tools and annotation verifiers.We present VST-A, a foundationally sound verification tool, that is implemented on top of VST.VST-A enjoys rich assertion languages and flexible proof strategies, and it allows users to write readable assertion annotations directly as comments exactly as in Fig. 1.
We illustrate the VST-A workflow in Fig. 2: (1) Users first provide a C program with assertion annotations.(2a) Our front-end parser will then convert the source code into ClightA, the Coq representation of this annotated C language.(2b) Next, the C program's functional correctness is reduced to smaller proof goals using the annotations.Specifically, a split function accepts a ClightA program and its pre-/post-conditions as input, and returns a set of straightline Hoare triples, each of which consists of a sequence of primary statements 2 and/or assume commands.For example, Fig. 3 shows the split result of the reverse function in Fig. 1 (2) (3) (...) Straight line Hoare triples' proof ... . (2) (3) (...)

Functional correctness proof
Step 1 (user's input) Step 2 (automated) Step 3 (user's input) Step 4 (automated) The split function The split function's soundness proof Fig. 2. Verification workflow in VST-A goals.As illustrated by the control flow graph in Fig. 4, the functionality of this split function is natural; it computes all of the control flow paths that are separated by assertion annotations in the source program.(3) Finally, users are left to prove each straightline Hoare triple in the split result.(4) The VST-A soundness theorem ensures the correctness of the original program if all of the paths have been verified.In summary, the contributions of this paper are: (1) A new framework for program verification that combines the benefits of interactive provers and the readability of annotated programs.(2) A formal language for annotated programs: We define ClightA, a formal language for annotated C programs, as a method of describing how the functional correctness proof for a large program can be reduced.(3) A control-flow-based verification splitting algorithm: The VST-A proof reduction framework uses a split function that is implemented in Coq and proved sound w.r.t. the VST program logic.We believe this split algorithm and its soundness proof are general and can be applied to other Hoare-style imperative verification tools as well.
(1) ∀ , ll (p, ) In the rest part of this paper, we will introduce background information about VST-A in §2.We will present our annotation-based proof language and analyze its expressiveness in §3.We will define the split function and prove it sound in §4.We will discuss the connection between our soundness proof and Hoare logic's conjunction rule in §5.We put statistics of VST-A verification examples in §6.In the end, we will discuss related works in §7 and conclude in §8.

BACKGROUND
We used Coq [Boutillier et al. 2014] and VST to implement our annotation verifier, VST-A.VST is an interactive program verification tool that is built in Coq.Its primary components are (1) Verifiable C ( §2.2, §2.3), an impredicative higher-order concurrent separation logic that is defined for an abstract C language called Clight ( §2.1), (2) VST-Floyd ( §2.4) [Cao et al. 2018], a proof automation system for forward symbolic executionbased verification that efficiently applies VST to real-world C program verification, (3) A machine-checked soundness proof of Verifiable C in terms of CompCert Clight semantics.
Together with the correctness proof of the verified C compiler-CompCert [Leroy 2009], we can obtain the foundational soundness of VST-A w.r.t. the assembly language.

Clight: abstract C language
We reason about C programs using CompCert Clight's syntax and semantics.Fig. 5 shows a simplified version of its syntax. 3Clight expressions are side-effect free.CompCert Clight distinguishes assignment statements and function call statements from other statements, and we refer to them as primary statements, since they are the basic building blocks for our VST-A development.
CompCert Clight uses loop ( incr )  as a general way to describe loops, and it is equivalent to for (; ;  incr ) {}.Three kinds of loops in C language, namely for, while, and do-while loops, can be expressed using this general loop statement (along with break and continue statements).In the paper presentation, we assume that return statements do not return a value, while our Coq development does handle return statements with return values. 4and  ret when the statement exits with break, continue, or return, respectively.We use ì  as an abbreviation of the last three postconditions.

Hoare logic for C programs
In Verifiable C, most of the compositional rules are standard.Fig. 6 shows some representative ones. 5To fit the general loop syntax of Clight, the Semax-Loop rule has two invariants, loop invariant  and continue invariant  con .The loop invariant  is required to hold before each iteration, 3 VST-A does not support goto statements, since they are not supported by VST's program logic.VST-A does not support switch statements for now, but it will be easy to add that to VST-A in the future.VST-A does not yet support CompCert's special calls to built-in functions; these are rarely used in the source language. 4VST handles return values by a reserved variable called ret, as we illustrated in Fig. 3. Supporting return statements with return values does not cause significant difficulties in our development. 5A full list of rules can be found in the appendix.
Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.and  con is required to hold at continue or before  incr statements.Verifiable C's proof rules for primary statements are less important -VST provides verified forward symbolic execution ( §2.4), and thus VST's users do not need to use those rules directly.All proof rules in Verifiable C are proved sound foundationally w.r.t. the CompCert Clight semantics, and symbolic execution applies the proof rules.VST also provides some useful derived rules, a few of which are shown in Fig. 7.The Seq-Assoc rule reassociates sequential compositions, and rules Extract-Exists and Extract-Pure introduce variables and propositions from precondition to context, respectively.

Inversion rules and weakest preconditions
The VST program logic is higher-order, i.e., assertions can be quantified over assertions, so that one can state the following inversion lemmas, which have already been proven in VST. 6  Lemma 1 (Inversion on seqencing).
It is worth mentioning that the normal postcondition appearing in the inversion on sequencing is VST's representation of weakest precondition, i.e. wp , , ì This definition of weakest precondition satisfies basic properties like the following: Thus, lemma 1 can be restated as: We utilize higher-order assertions in our VST-A development.
6 Readers who knew that VST's separation Hoare logic was proved sound with a semantic proof in a shallow-embedded style may be surprised that it is possible to prove inversion lemmas.But in fact, several years ago the VST-Floyd developers layered a deep-embedded Hoare logic over the shallow-embedded Hoare logic just so that useful lemmas of this kind can be supported.

Forward symbolic execution
VST's forward verification tactics enable users to obtain the strongest postcondition of a sequence of primary statements automatically.For example, in the verification of path (3) in Fig. 3, the symbolic assignment executor ae(, ) for precondition  and statement  can compute the following (where ll is the linked-list predicate): The assignment executor may fail, if the precondition  cannot guarantee that  will run safely or  is not in a good form7 such that the symbolic executor can execute , but we have found if users write assertions in their annotations, corresponding to correct proofs, the symbolic executor can run through the entire straightline Hoare triple.Users are left to prove the entailment from the inferred strongest postcondition (of the straight-line code) to the specified postcondition (arising from the "next" annotation, according to the split function).VST-Floyd also provides useful tactics for solving the entailment problem.Residual proof goals may be generated if some entailments cannot be automatically proven, and users can write their own flexible Coq proof scripts to address them.In summary, with the help of VST-Floyd, the back-end verification of the split results obtained by VST-A can be largely automated.represents a parameterized pre-/postcondition, i.e. it states that for any list of values ì  of type ì , if the initial program state satisfies  ( ì ) then the C function can be safely executed (no C undefined behavior will happen).If it terminates, the ending state satisfies  ( ì ).For functional correctness proofs, users can describe the main proof skeleton by inserting assertions (including loop invariants) and "given" annotations in the source program.We formally define this annotated C language (Fig. 8), namely ClightA, and implement a front-end parser that converts annotated C programs into the ClightA abstract syntax.Compared with the Clight syntax, ClightA has two new components: assertions and ExGiven structures.
As for assertions, users can insert them anywhere by writing /*@ Assert ... */ in the source program, which is directly converted into a leaf node in the ClightA syntax tree.Annotating loop structures with invariants is not compulsory in VST-A, as mentioned above, but users can still write loop invariants as /*@ Inv ... */ in C source files, to distinguish from assertions before loops.Our front-end automatically converts such annotations into the general syntax of ClightA.
As for the ExGiven structures, users can use a combination of /*@ Assert ∃ x, ... */ and /*@ Given x */ to represent logical variable introduction in a Hoare logic proof.In a typical goal-directed proof strategy, one can extract existential variables from the precondition into the assertion :

Expressiveness: describing Hoare logic proofs in VST-A
It is not surprising that the ClightA language is expressive enough for describing the main structures of Hoare logic proofs.Hoare logic proof rules decompose the verification target into smaller ones (e.g., as in Fig. 6 in  §2).Describing such proofs in VST-A is natural.For example, to apply the sequence rule Semax-Seq, we can insert the middle condition as an assertion annotation into the C program.

Expressiveness: describing interactive proofs in VST-A
ClightA is also expressive enough to describe interactive proofs used by existing verification tools like VST.In most cases, these tools try to find proof rules to apply using a goal-directed strategy.For example: • In order to verify a Hoare triple of form { }x = ;  , ì  ′ in VST, its forward symbolic execution tactic applies the sequence rule Semax-Seq and uses the strongest postcondition of  and x = e as the middle condition.VST-A's users do not need to write any annotation to describe such proof strategy.
VST-A 1:9 • In order to verify a Hoare triple of form in VST (suppose no break statements appear in  1 ), it asks users to provide a loop invariant  and then  ∧  = 0 will be used as the middle condition between the loop and  2 .In VST-A, the proof effort is similar.Users only need to provide such a loop invariant  in annotated C programs.
• In order to verify a Hoare triple whose precondition is existentially quantified, one will typically extract that existential variable into the proof context.The "given" annotation in VST-A describes this proof strategy.
Besides these goal-directed proof steps, users of interactive program-verification tools may use the rule of consequence anywhere in the middle of a proof, to replace a precondition with a weaker assertion (Fig. 10 shows an example).Correspondingly, users of VST-A can write assertion annotation to "invoke" the rule of consequence.In summary, typical interactive proofs are structural, following the syntax tree of the C statements (with local uses of existential variable extraction or the rule of consequence).The ClightA language is able to describe such structural proofs.

Expressiveness: describing nonstructural proofs in VST-A
ClightA can also describe nonstructural proofs, those that do not follow the C syntax tree.Allowing users to write nonstructural proofs is a considerable convenience.For example, in order to verify a Hoare triple of form in an interactive verification tool, users will probably be asked to provide a join condition after the if-statetment (a precondition for  3 ).In some cases this is appropriate and convenient, but sometimes it is both difficult and unnecessary.Here are some typical scenarios.
• The if-then branch  1 is the break statement.In this case, it suffices to prove and users may hope to symbolically execute  2 so that the middle condition between  2 and  3 can be generated instead of manually provided.8• The statement  3 is very short.In this case, verifying  3 twice can be less work than writing down a join condition.
• Proving  3 functionally correct needs very different proof strategy when  has a different boolean value.In this case, a join condition does not help to reduce the workload: in effect, one is case-splitting on  and then proving  3 twice.Similarly, in order to verify a Hoare triple of form where break statements appear in some branches in the loop body  1 , a join condition is needed (in addition to a loop invariant) in an interactive proof, since an execution may leave the loop by a break statement or by falsifying the loop condition .In VST-A, users can choose to provide a join condition (the proof will be structural) or not to provide a join condition (the proof will be nonstructural).
Certain kinds of generalized loop invariants lead to nonstructural proofs.Consider a red-black tree (RBT) algorithm that reestablishes red-black invariants after insertion.Fig. 11 is a straightforward textbook implementation of this algorithm [Cormen et al. 2022].When the rotation on line 15 is done, the loop exits immediately, since the assignment statement on line 14 ensures that the loop condition will be evaluated to false in the next iteration.If we were to write a loop invariant on line 3 before the loop condition is checked, we then need to both state the RBT bottom-up fixing invariant and describe the case in which the RBT has been fixed by the final rotation and should exit immediately.One may instead expect a single invariant on line 5, and reason separately about the control flow where the loop exits after a rotation.Such proof strategy is unavailable in common goal-directed verifiers, in which loop invariants are compulsory, but it is supported by VST-A.

CONTROL FLOW SPLITTING AND SOUNDNESS
One of the most important components in VST-A is the verified split function which reduces an entire C program's functional correctness to a series of straightline Hoare triples, based on the annotations.Intuitively, this split function is a CFG-based computation (like our demonstration in Fig. 4), and its soundness must ultimately relate to C's small-step semantics-any execution trace of the program statement can be decomposed into these separated paths.However, we choose to prove this soundness theorem directly using VST's program logic (Verifiable C), instead of proving it indirectly by first showing the execution trace decomposition lemma that we mentioned above.
Our main consideration is: it is nontrivial to formally establish a theoretical connection between a program logic and an operational semantics, especially when a complicated program logic for a realistic programming language with a lot of subtleties are considered.VST has already done that once-Verifiable C's soundness proof takes 60K lines of Coq definitions and proofs.If we would choose to prove the split function sound using small-step semantics as intermediate proof steps, we might need to develop similar lengthy proofs.
Even in applications to other languages and other operational semantics, it will be useful to build annotation verifiers on top of program logics in the way that we present here, rather than try to prove soundness directly from operational semantics.Proved-sound verification tools tend to be based on similar program logics (at least in terms of the core rules targeted by our split function: the sequence rule, the consequence rule, etc.).But they may be based on quite different styles of operational semantics (e.g.imperative HOL [Bulwahn et al. 2008] uses big step semantics and CompCert Clight uses small step semantics), or they may (like VST [Appel et al. 2014] or Iris [Jung et al. 2018]) incorporate modal impredicativity.
Since most Hoare logic proof rules are syntax-oriented, we implement our split function through recursion on ClightA syntax tree ( §4.2) and then we prove it sound by induction ( §4.3).In the rest part of this section, we will start from defining the Coq type of split results ( §4.1).

The type of split result
As illustrated in Fig. 12, control flow paths between the assertions can be divided into four classes: (1) head paths -control flow paths from the precondition to an internal assertion; (2) tail paths -control flow paths from an internal assertion to the postcondition; (3) full paths -control flow paths between two internal assertions; (4) assertion-free paths -control flow paths from the precondition to the postcondition (we call them assertion-free since they pass through no assertions inside the annotated program).Formally, the split result is a record that consists of "head/tail paths", "full paths" and "assertionfree paths", which are essentially a list of basic program statements ì   annotated with one single assertion, two assertions, and no assertions, respectively.A basic statement can either be a primary Clight statement,   , or a special statement, assume , that represents an if-condition (positively or negatively) in the control flow.
Recall that in the VST program logic, a Hoare triple has multiple postconditions for different kinds of program exits (i.e., exit by break, by continue, by return,or normal fall-through).Correspondingly, the split result also makes distinctions among the different exits.Thus, in the definition of our intermediate split result, the record contains one set of "full paths" between the annotated assertions, one set of "head paths" from the entry point to the internal assertions, four sets of "tail paths" from the internal assertions to the four different kinds of exits, and four sets of "assertion-free paths" from the entry point to the four different kinds of exits.To handle existential variables in their scope, full paths can be universally quantified.With these fields, the split result record can sufficiently reveal all control flow information in a ClightA program.Fig. 13 shows the definition.
By supplementing "head/tail paths" or "assertion-free paths" with the pre-/postconditions, we can interpret the split result into a collection of closed Hoare triples as hypotheses of split's soundness theorem (these hypotheses are illustrated in Fig. 12 and formally defined in Fig. 14): Basic statement : , where Theorem 4 (Soundness).For any ClightA program  and pre-/post-conditions , ,  brk ,  con and a) all straightline Hoare triples from the precondition  to internal assertions are provable, (b) all straightline Hoare triples from internal assertions to the postconditions ì  are provable, (c) all straightline Hoare triples between internal assertions are provable, (d) all straightline Hoare triples from the precondition  to the postconditions ì  are provable, i.e. (defined in Fig. 14  Remark.CompCert Clight does not have an assume statement.We choose to encode the assume statement into Clight AST, and encode straightline Hoare triples into VST Hoare triples, so that VST-A's users can directly use VST's tactics to prove those straightline triples.Specifically, assume  ≜ if () skip else break; { }  1 ;  2 ; ...;   { } ≜ { }  1 ;  2 ; ...;   {, [⊤, ⊥, ⊥]} .

Split function
The core split function is defined by recursion on the abstract syntax tree of the input ClightA program.Note that the split function is a partial function, since we will not try to compute the reduction result if there is an assertion-free loop in the CFG.
Base cases (Fig. 15).For primary statements   , the normal assertion-free path set (the  nor − field) is a singleton of the statement itself, i.e. {[  ]}, and all other path sets are empty.For a break statement, the assertion-free break-exit path set is a singleton of an empty list of basic statements, i.e. {[]}.The split results of continue and return are similar.For the assertion annotation assert , the split result has only one head path {[]− { }} and one normal tail path {{ } −[]}.
Recursion cases.Using sequential composition as an example, all "full paths" between the assertions in  1 ;  2 can be divided into three classes (Fig. 16): (1) paths that completely fall in the CFG of  1 ; (2) paths that completely fall in the CFG of  2 ; and (3) paths that combine two parts, one of which is a "tail path" of  1 and the other of which is a "head path" of  2 .Such "tail paths" and "head paths" of an assertion-annotated C program can also be recursively computed.We show our complete definition in Fig. 17.
. In this definition, we use • to represent the concatenation of two paths, and overload this notation to connect two sets of paths: where Computing split(if ()  1 else  2 ) simply adds assume statements to the head of all head paths and assertion-free paths in the two if-branches, and returns the union of the two split results.Computing split(loop ( 2 )  1 ) is similar.Detailed definitions can be found in the appendix and our Coq development.
Handling logical variables (Fig. 18).The focus of computing split(ExGiven  : , { ()}  1 ) is to handle the logical variable .(1) The ExGiven structure has an existentially quantified precondition  () in the head.Therefore, there are no assertion-free paths in the split result, and the result of the Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.(2) The tail paths in ExGiven  : , { ()}  1 can either be paths from the precondition ∃ : . () to exits of  1 or tail paths of  1 itself.When these tail paths connect to head paths later, the postconditions of those head paths will not be in the scope of .Thus, we existentially quantify over the variable  in all those tail paths' preconditions now, i.e. if { ()} −ì   is a tail path of  1 , then {∃ : . ()} −ì   is a tail path of ExGiven  : , { ()}  1 .

VST-
(3) In full paths, we need to unify the existential variable  in  with those in the head paths that are split from  1 , so that  can be shared among the pre-/post-conditions of each combined full path.Full paths in  1 ( ⊢⊣ ) are also collected after adding a universal binder  to the result.In our Coq development, we implement this definition using Coq dependent types.We put technique details in the appendix.

Proof of soundness
We prove theorem 4 by induction over ClightA syntax trees.For the convenience of presentation, we use AllHypo(, ì , split()) to represent all ten hypotheses of this soudnenss theorem: In order words, the soundness theorem says: AllHypo(, ì , split()) implies { }  ⇓ ì  .
AllHypo , , [ brk ,  con ,  ret ] , . In this proof, only induction steps about sequential compositions, if-statements and loops are interesting.We describe the main idea of proving split( 1 ;  2 ) sound in this section.The proofs for if-statements and loops are similar and can be found in the appendix.Fig. 19 shows an example of sequential composition with precondition , postcondition , and annotations  1 ,  2 ,  1 ,  2 .The split function will generate these straightline Hoare triples: In order to prove { }  1 ⇓ ;  2 ⇓ { }, we need to find an intermediate assertion  such that { }  1 ⇓ {} and {}  2 ⇓ { } (according to Semax-Seq).These two judgments can be established by induction hypothesis and the following four Hoare triples: These requirements can be simply satisfied if we let  be wp(assume ;  2 ,  1 ) ∧ wp(assume !;  2 ,  2 ).
In the general case, we instantiate  to the conjunction of the weakest preconditions of all head paths and assertion-free paths in split( 2 ).Using VST's higher order logic, this middle condition can be written as: According to the induction hypothesis, it suffices to prove the following two propositions.
For proposition 1, the inversion lemma for sequencing (Lemma 1) has already shown that the weakest precondition of the second statement can serve as the intermediate assertion for the sequential composition.Based on Lemma 1, we can prove a corresponding inversion lemma on the • operator for each type of path in the split results.(3) If TailHypo(,  ⊢ •  − ), then TailHypo(, ∃. ∧ AssnFreeHypo(, ,  − ),  ⊢ ) (4) If FullHypo( ⊢ •  ⊣ ), then TailHypo(∃. ∧ HeadHypo(,  ⊣ ),  ⊢ ) According to the conjunction rule, AssnFreeHypo and TailHypo from Proposition 8 still hold if we combine all of those weakest preconditions of  2 's partial paths (i.e.those preconditions are such that AssnFreeHypo(, ,  − ) and HeadHypo(,  ⊣ )).Formally, Proposition 8 can be extended into the following form by the conjunction rule.
Proposition 9 (Grouped Inversion lemmas). (1 Next, we also use the conjunction rule to combine the weakest preconditions of the different kinds of paths and prove proposition 1, which completes the soundness proof of split( 1 ;  2 ).
However, the conjunction rule is not ubiquitous among the Hoare logic variants proposed in the literature: for example, the current VST program logic cannot derive the conjunction rule.We will leave the discussion of the conjunction rule to §5.For now, we assume that the conjunction rule holds, so that Proposition 9 and the proof of split( 1 ;  2 ) soundness can hold.

CONJUNCTION RULE AND PRECISENESS
The conjunction rule is natural in traditional Hoare logics and separation logics for sequential programs, but some extensions to the logics will make the conjunction rule inadmissible.In this section, we more extensively discuss why the conjunction rule is required by our soundness proof ( §5.1, §5.2).To make the conjunction rule admissible in VST-A ( §5.3, §5.4,§5.5), we identify a new notion of preciseness to restrict the function specifications being called during verification.We also discuss the trade-offs of using conjunction rules and precise function specifications, and we suggest some future directions for improvement ( §5.6).

A small example
Suppose we would like to prove the Hoare triple given that the following split results hold (here, we assume that  1 ,  2 and  3 are primary statements): By inversion on sequential composition (lemma 1), proposition (3), ( 4) and ( 5 Thus, the split function's soundness on the example above can be reduced to an instance of the conjunction rule.

Unsoundness when the conjunction rule is inadmissible
So far, we have seen a tight connection between the conjunction rule and the split function's soundness -our soundness proof uses the conjunction rule ( §4.3) and a very simple instance of this soundness theorem can be reduced to an instance of the conjunction rule ( §5.1).But what will happen to split's soundness if the conjunction rule is not admissible?Consider Hoare logic with ghost updates [Krebbers et al. 2017a] as an example.Ghost states are "logical states" that help with the program's proof, and they particularly useful for verifying concurrent programs. 9When users prove programs with ghost states, they can apply a ghost update when they use the consequence rule, see Semax-Conseq-Ghost below.Here, | ⇒  says: there is at least one possible ghost update which makes the state satisfy .
The conjunction rule is not admissible in this logic -if the proofs of { }  { } and { }  { ′ } use different and conflicting ghost updates, { }  { ∧  ′ } cannot be valid since two conflicting ghost updates cannot happen simultaneously.
In this logic, our split function is unsound.This loss of soundness is not determined by the way we prove soundness in §4.3 but by the framework we propose to first split the program into individual paths, which are then verified separately.Consider the following annoated program: in which  is a ghost location for storing the status of the following STS (state transition system) [Turon et al. 2013]: We assume that f(), 9 Ghost states are not the same as the "ghost variables" of traditional Hoare logics.
In this example, all straightline Hoare triples in split's result are provable, especially the following two triples about f(): For the first triple, we can choose to take the  →  1 step in ths STS before calling f().For the second triple, we can choose to take the  →  0 step in ths STS before calling f().However, the whole Hoare triple is not provable since we cannot determine the value of x before x = nondetermined_0_or_1() is executed.That breaks split's soundness.

VST-A's design choice and proof strategy
In the current design of VST-A, we focus on sequential program verification and disallow all ghost updates.VST-A uses a more restricted variant of the VST program logic.This variant is still proved sound w.r.t.CompCert Clight semantics and the most significant change is that the ghost update operator is removed from the consequence rule.Despite the removal of ghost updates, users are still able to write unrestricted higher-order predicates and prove many complex sequential programs in VST-A.We derive the conjunction rule (theorem 10) from this stronger logic by induction over Clight abstract syntax tree.Our inductive proof steps are all Hoare-logic-based, and we believe that such a proof strategy is (1) easier to formalize in Coq, and (2) in fact more general than a semantic-based proof, since it is independent of how the soundness of the Hoare logic was proved w.r.t.its semantic model.
Consider the induction step for  =  1 ;  2 .By applying Lemma 1 to the premises we obtain the following: We can apply the induction hypothesis of  1 and make use of Semax-Conseq to obtain the following: where  can be instantiated as  1 ∧  2 .According to Semax-Seq, we are left to prove: Using Extract-Exists and Extract-Prop, it suffices to prove: which immediate follows the induction hypothesis of  2 .
For other induction steps (e.g. for if-statements and for loops), the proof idea is somewhat similar in that their corresponding inversion lemmas are first applied, then the induction hypotheses can be used to complete the proof.For control flow statements (break, continue and return), the proof is trivial.Thus, we are only left to derive the conjunction rules for primary statements.

The conjunction rule for memory stores
Consider a simple store statement and corresponding proof rule in VST.
The precondition states that the heap can be split into two parts.One part is a singleton heap, in which the nonaddressable variable  is a pointer to the old value , and it is described by the mapsto predicate  ↦ → .The other part should satisfy the postcondition  when joined with a singleton heap that stores the new value  ′ .
Note that in this specification, the old value  is universally quantified.If we want to conjoin the postconditions of two Hoare triples about *  =  ′ , we need to unify the two different instantiations of  into one.To be specific, the following property needs to hold: Proposition 11 (Preciseness of store).
In VST-A, we prove this through semantic-model-level reasoning based on its underlying memory model.Then, the conjunction rule for such simplified store statements can be logically derived.We leave the detailed proof for the appendix.
In VST, assignment statements  1 :=  2 include (1) assigning a value to a nonaddressable variable, (2) loading a value from memory to a nonaddressable variable and (3) storing a value in memory.VST's higher-order assertion language also supports separation logic predicates with fractional permissions.In VST-A, we have proved that the conjunction rule holds for all primary set/load/store operations.

The conjunction rule for function calls
The store rule mentioned above can be treated as a specification for a function call that contains only a single store instruction.We have shown that Proposition 11 is an important property for deriving the conjunction rule.In this section, we generalize this property, which we refer to as precise function specifications, to derive the conjunction rule for function calls.
As previously mentioned, VST-A function specification has a form of where  is a precondition parameterized by a list of formal parameters ì ,  is a postcondition parameterized by the return value  , and types ì  represents the type of logical values to be shared between  and .Therefore, both  and  will also be abstracted over a set of logical variables ì  typed ì .We define the precise function specifications as follows: Definition 12 (Precise function specification Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.

VST-A 1:21
In VST-A's program logic (a variant of VST's program logic), the function call rule, Semax-Call, requires the callee function specification to be precise.
The notion of "precise function specification" that we propose is defined with respect to an operation (e.g. a store statement or a function call), while traditionally, "preciseness" is defined for a predicate.A typical example of a precise predicate is the  ↦ →  predicate used in Semax-Store.
Definition 13 (Precise predicate).A predicate  is precise if for any  1 ,  2 , In fact, our definition of precise function specifications includes a common use case of precise predicates.As proved by Gotsman et al. [2011] and Vafeiadis [2011], if the conjunction rule is expected in concurrent separation logic with locks, the resource invariant for locks should be a precise predicate.Consider the following specification for a release operation for lock : where  is the predicate that describes the locked memory.We use locked  and unlocked  to denote whether this memory is owned by the current thread or not;  is a fractional permission.It is clear that if the resource invariant  is a precise predicate, then one can show that this specification is a precise specification.The precise function specification also makes a difference in the sequential setting we are considering.One example of a non-precise function specification is: Here  is a predicate dependent on a logical variable  describing the frames that the function foo does not modify.It is possible to find such  that holds for two different instantiations of , and breaks the precise function specification requirement.
In our Coq development, we find that, for most function specifications, we can derive that for any formal parameters ì , logical variables ì  1 , ì  2 : Then, to prove a specification precise, it is sufficient to show that  is a precise predicate.A typical example is the store predicate "↦ →".Clearly: For linked lists' representation predicate ll, we also have: Moreoever, property (7) is composable.For example, in order to prove: we can first use (8) to derive  1 =  2 , then use after (9) to derive  1 =  2 .
It is well-known that precise predicates are also composable, thus we developed several extensible automatic tactics in VST-A to help users prove the precise specification by combining property (7) and predicates' preciseness. 10 We believe that most C functions have specifications that are naturally precise.Non-precise specifications are usually caused by separation logic conjuncts that describe inaccessible memory slices to the function, such as the  () proposition in the example above.In practical verification tasks, one can usually remove these conjuncts using the "frame rule" and obtain a precise specification.The pruned part of the specification can be expressed elsewhere in the program, where the memory slice is really used.

Discussion and future work
In our current design of VST-A, we require the conjunction rule to be derivable in the logic to ensure the splitting algorithm's soundness.We do not consider our current design choice as a fatal problem for future extensions.Here are some potential research directions that may support ghost updates or remove the restriction of only using precise specifications in the future.
Verify the sequential fragments of a concurrent program.As was discussed in §5.2, the existence of the conjunction rule forbids the use of ghost updates.However, there are ways to verify the sequential fragment of a concurrent program with VST-A.We have proved the following property: , where | ⇒ indicates that the triple is derivable from the original VST logic that supports ghost state updates, then there exists , ì The triple about  1 is derivable from the stronger program logic in VST-A.We can apply the current VST-A framework to the sequential fragment  1 , and leave the rest of the program to be verified by the full-power VST interactive verifier.
New annotations for ghost actions.Another possible direction is to improve the capability of annotations in terms of expressing proofs.For example, in future versions of VST-A, we could allow users to write either explicit ghost commands in their programs, or write two consecutive assertions where the latter can be derived from the former with a ghost update.As a result, we do not need to worry about the possibility of having two conflicting ghost updates simultaneously.
Additional annotations for function calls.As was discussed in §5.5, in order to derive the conjunction rule for function calls, the callee should meet the requirement of precise function specification.However, this requirement could be removed if the logical variables of the specification are explicitly instantiated.This avoids the possibility that proofs of multiple straightline Hoare triples containing the same function call could actually instantiate those logical variables differently, causing issues in proving the conjunction rule.Intuitively, this is like putting two assertions before and after the function call statement as pseudo "join points" in the control flow, ensuring that every function call would only appear in one unique path in the split result.Then, split's soundness would be trivial for function calls.
Prophecy variables.In our counterexample in §5.2, the verification target is unprovable but will become provable if we are allowed to insert a prophecy variable [Abadi and Lamport 1988; Jung   10 In several cases only weaker equivalence relations can be derived from the conjunction.Our tactics also support proof automation for that, if this relation implies the equality of the specification Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.
VST-A 1:23 et al. 2020] into the program.In general, we may be able to prove split's soundness with the help of prophecy variables even if ghost updates are permitted in the logic.

EVALUATION
In this section we evaluate VST-A in various aspects, including verification effort, verification time, and statistics of our development.We also draw a comparison between VST-A and VST.
Verification effort using VST-A.We test VST-A using several sets of programs and present the statistics about verification effort in Table 1.VST-A proofs are divided into two parts: the annotated programs (which describe the main idea of the proof in a way easier to read than VST proofs) and Coq verification of straightline Hoare triples (similar to corresponding parts of VST proofs).Therefore, we count the lines of annotations and proofs separately, in the Specification and Assertion columns and the Proof column, respectively.
Our benchmarks include some measured by Sammler et al. [2021], including linked lists and binary search trees.We also include a slightly larger example-a small-step interpreter of a toy imperative language, which includes a number of if-branches.It can be seen from our evaluation that without providing additional assertions in the program code, VST-A is still able to split and verify the program.We also conduct a comparison in the proof effort between VST-A and VST.The last column in Table 1 shows the lines of proofs for verifying the same function in VST; compare this to the "Specification+Assertion+VST-A Proof" columns.The proof lines do not count auxiliary predicate definitions and lemmas since they are the same in VST and VST-A.

VST-
Not manifest in the line-count, but still important, is that the VST user must learn to use several different tactics for different forms of control flow, and each of these has several options depending on which assertions are supplied (e.g., if-postcondition, for-loop continue assertion, for-loop break assertion, etc.).In contrast, VST-A's control-flow splitting takes care of all of this, leaving the user to learn only the straight-line forward and forward_call tactics.In some cases, it makes Coq proof easier to automate.For example, verifying the interpreter example in Table 1 using VST-A only needs to use forward to handle assumes repeatedly, but this proof strategy cannot be used in the corresponding VST proof.
To summarize, we believe that to verify the same program, the manual effort of VST-A is no more than that of VST, while in the meantime VST-A can provide more intuitive and readable proofs with annotations in the C source program.
Verification time for different phases in VST-A.The verification time is shown in reduction time is short.For different verification tasks, users need to provide different separation logic predicates and lemmas to specify and prove the program.This consitutes a majority of the compilation time, which is also needed by a VST proof.A significant difference between VST-A and VST is that in VST-A each straightline Hoare triple's proof can be developed as a separate lemma, residing in distinct files, facilitating parallel checking and compilation.Therefore, we present the average time for these phases and the number of paths.In VST, the correctness theorem of a function needs to be proved as a whole, which is more difficult to parallelize.
In addition to parallelized compilation, the separation of straightline Hoare triple proofs also makes it easier to maintain the correctness of the proofs when the program is modified.Only those paths that are affected by the modification need to be re-verified.We conducted several experiments and the results are shown in Table 3  Many annotation verifiers work by reducing annotated programs to SMT assertion entailments: these include Frama-C, Dafny, VeriFast, Viper, Hip/Sleek.Some of those systems use a specialized intermediate language for verification, connectable to several SMT back-ends and to several programming-language front-ends; for example Frama-C uses Why3 [Filliâtre and Paskevich 2013] and Dafny uses Boogie [Barnett et al. 2006].Modern SMT solvers effectively and efficiently solve many of the resulting entailments.These annotation verifiers are, in practice, "interactive:" one starts by annotating the program with function specifications and some loop invariants, and the verifier inevitably points out several places where the proof fails-with sufficiently good error messages that the user can adjust the assertions, add new assertions and invariants, and try again, and again.This works well in practice, and it is what we wanted to emulate.The disadvantage of those systems is in the poverty of their assertion languages.Because SMT (or Why3 or Boogie) accommodates only first-order or near-first-order logics, the rich specification languages of VST or of logics built in Iris cannot be used.In VST one often proves high-level properties about the behavior of programs, in application-specific mathematics that would be difficult to fit into SMT.In Frama-C, Dafny and CN (etc.), some authors work around this by writing higher-level proofs in Coq and program-logic proofs in the annotation verifier, and stapling together the two verifications (in different logics without a common foundation) [Boldo et al. 2014].That approach could be made more foundational by embedding a semantics of Why3 in Coq [Cohen and Johnson-Freyd 2024], but the assertion language would still be near-first-order.
Another disadvantage of those annotation verifiers is that none has a machine-checked proof of soundness (e.g., w.r.t. an operational semantics).This lack is not inherent, as VST-A demonstrates.
When proving programs in VST or in an Iris-based logic, one generally uses automated solvers to prove entailments, or at least to prove the easy parts and leave residuals for the user.These solvers may be programmed in Coq (using tactics or computational reflection) or may be external (such as SMT).VST-A does not choose a specific solver.Users can choose their own solver to prove the split result correct.
BRiCk [Malecha et al. 2022], used by BedRock Systems Inc. to verify its microkernel/hyperviser, is a program logic for C++, built in Iris, on principles inspired by VST.We expect that our VST-A method would work well in such a C++ program logic.
Tools like F* [Martínez et al. 2019], LiquidHaskell [Vazou et al. 2014[Vazou et al. , 2018]], and ATS [Chen and Xi 2005] have managed to combine higher-order programming with theorem proving in a dependent type system so that rich higher-order properties can be automatically verified in the style of the program's annotations.However, they require users to either write programs in a new domain-specific language or construct the proof as a term in the program.By contrast, VST-A works on the standard (and practical) C programming language while also enabling reasoning with higher-order properties.
We also note the work of sledgehammer [Böhme and Nipkow 2010] and auto2 [Zhan 2016] for proof automation in Isabelle.Sledgehammer relies on SMT solvers, while auto2 builds compositional proof automation using a saturation-based proof automation system in which goal-directed proof strategies can be encoded.Users of auto2 can easily extend auto2 with their own domain-specific proof strategy.Zhan [2018] built an auto2 instance that supports separation logic reasoning for verifying sequential programs.Although auto2 supports flexible saturation-based proof strategies, this specific instance of sequential program verification is mainly goal-directed.VST-A is not goal-directed, and is open to any solver or proof style when verifying entailments, regardless of whether it is, based on an interactive proof a tactic-based solver, or a model checking based one.

Interactive prover-based program verification
There is no deep reason why annotation verifiers should lack soundness proofs (e.g., Frama-C, Dafny, Verifast) and tactic-based verifiers should have machine-checked soundness proofs (e.g., VST, Iris).We guess that the reason is: such soundness proofs are naturally higher-order, more easily accommodated in the kinds of higher-order logics implemented in proof assistants such as Coq and Isabelle, so it is natural that designers of VST and Iris also have their users operate in the same proof assistants.
Soundness proofs are important for real-world programming languages, which have many subtle features in their semantics and compilers.Users want what they prove about a program to be consistent with the compiled machine code semantics, so the foundational soundness of VST-A is a real benefit.In this section, we compare VST-A with other foundational tools.
VST and various Iris-based verifiers have invested significantly in increasing proof automation so that users can verify their programs conveniently.However, they all require their users to complete correctness proofs for the entire program in an interactive theorem prover, which is not easy for an ordinary software engineer to learn.
There are also works that build annotation verification into interactive theorem provers and have achieved foundational soundness.RefinedC [Sammler et al. 2021] is an automated and foundational annotation verifier for C programs, that defines a restricted fragment of the Iris logic, Lithium, so that proof searches can be guided by translating the assertion annotations into a Lithium program.DiaFrame [Mulder et al. 2022] is also an automated and foundational tool.It employs a similar structural approach to RefinedC, but is more targeted at proving fine-grained concurrent programs.These tools use tactic-based proof strategy design and achieve some reasonable automation.In other words, a Hoare triple will be reduced to smaller proof goals (and even directly solved) by automatically applying a series of proof tactics, which use proved-sound logic rules or verified single-step symbolic execution.In comparison, VST-A is based on one computational proved-sound reduction function.Thus, developers of VST-A do not need to decompose this reduction step into multiple proof tactics, which in the end allows users to describe more flexible proofs using annotated C programs.Here is an example of how reduction is decomposed into tactics.Given an annotated program of form (here, we use a pair of braces to emphasize that sequential composition is right associative in CompCert Clight and in our ClightA syntax): / * @  * / if ()  1 else  2 ; {  3 ; / * @  * /  4 } / * @  * / VST-A will generate 3 straightline Hoare triples: { }assume ;  1 ;  3 { } { }assume !;  2 ;  3 { } and { } 4 {}.
In order to achieve this in RefinedC's or DiaFrame's tactic-based proof automation system, the system needs to apply the Seq-Assoc rule (see §2.2) first, turning the proof goal into: { } { if ()  1 else  2 ;  3 } ;  4 {} and then apply the sequence rule with middle condition .After that, one more proof rule 11 is needed for turning if ()  1 else  2 ;  3 into if () {  1 ;  3 } else {  2 ;  3 } so that the Semax-If rule can be used to complete the reduction.However, such tactic-based decomposition is not always easy to design, and it can even be impossible.Especially, it is not obvious how to design tactic-based 11 In most Iris-based verifiers, this last step is not needed since Iris's symbolic execution can do that implicitly.
VST-A 1:27 proof automation for handling nontraditional loop invariants supported by VST-A.That is, our split function (in effect) performs some nontrivial static analysis and is verified by a nontrivial soundness proof.Besides supporting more flexible proofs, the core split function in VST-A is computationbased so that VST-A can first complete the reduction step very efficiently, and users can then prove straightline Hoare triples manually or using their own domain-specific proof automations.Also, this design of VST-A can better support incremental development.

Conjunction rule and preciseness
The conjunction rule is naturally sound in traditional Hoare logic [Floyd 1993].However, for concurrent separation logic with locks, a counterexample that leads to unsoundness [O'Hearn 2004] can be found.As a workaround, De Vilhena et al. [2020] proved a restricted version called the candidate rule, which requires postconditions to be pure (independent of resources, especially ghost resources) to solve their verification problem.However, this rule cannot be applied in our setting.In VST-A, we do not propose alternative rules but prove the conjunction rule on top of a VST logic without ghost updates.As for supporting concurrency, we proposed several possible directions in §5.6.We are not aware of any similar notions of precise function specifications in the literature as we have defined in this paper.Traditionally, preciseness restrictions are placed on assertion predicates.For example, in concurrent separation logic, the resource invariant should be a precise predicate to make the conjunction rule sound [Gotsman et al. 2011;Vafeiadis 2011].Our aim is to define a notion of preciseness for specifications, so that the conjunction rule is derivable from the existing logical rules.Compared with traditional preciseness, we showed in §5.5 that our notion of preciseness is more expressive, as it accounts for a pair of pre-/post-conditions for an operation and allows the specification to be quantified by logical variables.

CONCLUSION
We have presented VST-A, an annotation verifier that is foundationally verified.VST-A targets a widely used real-world language, C, and supports higher-order assertions in the very rich specification language of VST that includes the full expressive power of Coq.VST-A splits the verification of a large program into verifications of straightline control flow paths separated by assertions.The soundness of this approach requires the conjunction rule to be derivable in the program logic.We have identified a novel notion of precise specifications in the proof of the conjunction rule.Currently, VST-A only supports sequential C program verification, but we have proposed ways to extend VST-A to support concurrency in the future.Our formal annotation language and other major designs are not C-specific, nor are they separation-logic-specific, nor VST-specific.A similar development can be used to design other Hoare-style annotation verifiers for imperative languages.
Comparing to existing foundational program verification tools built in interactive theorem provers, VST-A has the following advantages: • Annotation-based proof is a more readable way to explain why a program is correct.
• Our annotation-based proof language ClightA is expressive enough to describe nonstructural proofs, which cannot be supported systematically using goal-directed tactic-based proof automation; • VST-A is easier to use -users only need to write assertions in annotations, and use forward symbolic execution to prove straightline Hoare triples.In comparison, users of other tools like VST and Iris need to use different tactics to handle different program structures like if-conditions, recursions and different loops.
• VST-A reduces proof recompilation time.When a verified program is updated slightly, its correctness proof also needs corresponding updates.In existing interactive verifiers, users must rerun all tactical proof scripts, even though only a small portion needs to be updated.Now, only the part of the program that has been changed and the corresponding proof need to be recompiled, since other parts in split's result are unchanged.We aim to enhance the verification process of VST-A further.Future work includes the introduction of domain-specific heuristics for automatically manipulating separation logic predicates during symbolic execution and proving separation logic entailments on straightline Hoare triples.We also plan to allow user to specify partial assertions, so that users do not need to write assertions for the entire state of the function throughout the whole program.

Fig. 7 .
Fig. 7. Derived rules from C Hoare logic C programs and internal representations VST-A requires users to describe C function specifications and C programs' functional correctness proofs by writing annotations in C programs.Specifically, a function specification in VST-A is always in a /*@ With ... Require ... Ensure ... */ form and With [ ì  : ì ] Require  ( ì ) Ensure  ( ì )

Fig. 10 .
Fig.10.Example of replacing the precondition with an existentially quantified assertion.The predicate ll (p, ) means that a linked list starts at address  representing (in the heads of its cons cells) the sequence .The two preconditions above are provably equivalent.

1Fig. 11 .
Fig. 11.The fix-up function of red-black tree insertion Fig. 12.Control flow graphs v.s.paths in split results

Fig. 13 .
Fig. 13.The type of split results

Fig
Fig. 15.Split function for basic statements

•
Statistics of the development of VST-A.We present the line of codes statistics for our development in VST-A below.Our development in VST-A includes the following parts: • Coq formalization of the restricted fragment of VST program logic, including the logical rules, auxiliary lemmas, and the conjunction rule proof: 6,236 lines • Coq formalization of the VST-A framework, including the split algorithm, and its soundness proof: 6,287 lines • Modification to the CompCert C parser to parse annotated C programs: 1.87% of change • Modification to the VST-Floyd lemmas and automation tactics to support forward symbolic execution in the restricted fragment of VST program logic: 6.74% of change Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.OCaml development that provides an efficient implementation of the split algorithm: 3 : four triples are returned as verification 1 struct list {unsigned head; struct list * tail;}; 2 struct list * reverse (struct list * p) { Assert ∃  1    2 . = rev( 1 )   2 ∧ v ↦ → (, ) * ll (w,  1 ) * ll (,  2 ) * / VST-A reuses VST's Hoare logic rules, which are known as Verifiable C. VST's Hoare judgment extends the postcondition into four parts to address control flow instructions such as break, continue and return.A judgment { }  {, [ brk ,  con ,  ret ]} can be interpreted as starting from a program state that satisfies .After executing , if the statement exits normally, the program state satisfies .Similarly, the program state should satisfy  brk ,  con , AssnFreeHypo(,  brk ,  brk − ), AssnFreeHypo(,  con ,  con − ) and AssnFreeHypo(,  ret ,  ret − ), then { }  ⇓ {, [ brk ,  con ,  ret ]}, where  ⇓ represents the result of erasing all annotations from .Noticing that all of the control flows in a function body should end with a return statement, we directly use the following corollary in VST-A.
{ ↦ → (if  then  1 else  0 Ghost variables are logical variables that were introduced to relate old values of variables to current values, and to relate current values to abstract mathematical values.VST and Iris support ghost variables using ordinary Coq variables; those ghost variables are fully compatible with our VST-A program decomposition, to support data abstraction and modular verification [Beringer 2021].Examples of such variables in Fig. 1 are ,  1 , , ,  2 .Proc.ACM Program.Lang., Vol. 1, No. submission to POPL, Article 1. Publication date: January 2024.

Table 1 .
Number of C functions, lines of C code (without annotations), VST-A specification lines annotated in the C comments, assertion annotations in the C comments, Coq proofs in VST-A, versus Coq specifications and proofs in VST.

Table 2 .
Since the splitting process in VST-A is a computationally proven sound function, it is not surprising that the

Table 2 .
Verification time for different phases (in seconds).Reduction: time used for parsing C and generating straightline paths in Coq.Common: time used for compiling common definitions and theorems.Avg.compile: the average time used for compiling the straightline Hoare triple definitions of all generated paths × the number of paths.Avg.verify: the average time used for verifying each path × the number of paths.Max.verify: the maximum time used for verifying each path.VST verify: the time used for verifying the same programs in VST.

Table 3 .
. Case study of the number of paths that need to be re-verified when the program is modified.