Introduction to IMP and to Hoare Logic
The language IMP
To study the verification problem in case of imperative programs we use a tiny programming
language called IMP or sometimes While in the literature (see below for some references).
The language has just one data type Val = ℕ, namely the natural numbers, but two sorts
of expressions, that is arithmetic and boolean expressions.
An IMP program is a command, whose syntax is defined by the grammar:
Com ∋ c, c' ::= SKIP | x := a | c :: c' | IF b THEN c ELSE c' | WHILE b DO c
This syntax is close to actual programming languages like Pascal or C, but for the
command SKIP, that simply terminates the exacution without any side effect, and the
notation c :: c' for the more familiar c ; c', forced to us by Agda.
The meaning of an IMP command is some transformation of the memory;
a memory state, or just a state, is simply represented as
a map s : State where State = Varname → Val, assigning a value to each variable identifier
in Varname.
The formal definition of such meanings is given here in terms of their operational
semantics, that is based on some sort of symbolic execution of the commands.
We describe the operational semantics of IMP in two ways. The first one is by means of
the relation ⦅ c , s ⦆ ⇒ t, where ⦅_,_⦆ ⇒ _ ⊆ (Com × State) × State. Such relation
is called big-step operational semantics because it relates the initial to
the final state of a terminating execution of a command:
⦅ c , s ⦆ ⇒ t if the execution of c when started in the state s terminates
yielding the state t
A second relation we consider is the small-step semantics, which is a relation
⦅ c , s ⦆ ⟶ ⦅ c' , t ⦆, where ⦅_,_⦆ ⟶ ⦅_,_⦆ ⊆ (Com × State) × (Com × State),
whose meaning is
⦅ c , s ⦆ ⟶ ⦅ c' , t ⦆ if the execution of the "first" command in c
leads in one step from state s to state t, and c' is the part of c
that remains to be executed (its "continuation")
where of course we shall make precise, among other things, which part of a command c is
its first one and which is its continuation.
As we shall see, both these relations are deterministic that is they are functional, in the sense that
if ⦅ c , s ⦆ ⇒ t then such a t is unique; similarly, if ⦅ c , s ⦆ ⟶ ⦅ c' , t ⦆
then ⦅ c' , t ⦆ is uniquely determined by ⦅ c , s ⦆.
Although different, the two semantics are related as stated in the theorem:
∀ c s t . ⦅ c , s ⦆ ⇒ t <==> ⦅ c , s ⦆ ⟶* ⦅ SKIP , t ⦆
where ⟶* is the least reflexive and transitive relation including ⟶.
The Floyd-Hoare logic
The second ingredient for program verification is a language of specifications. We adopt
assertions, that is formulas of first order arithmetic, enriched with program variables
from IMP. In our formalization an assertion P : Assn, where Assn = State → Set, is a predicate
of states; a pair P, Q of assertions are pre-condition and post-condition of a
command c in the triple [ P ] c [ Q ] (written {P} c {Q} in the literature,
a notation which is not permitted by Agda, alas) respectively.
A triple [ P ] c [ Q ] is valid, written |= [ P ] c [ Q ], if
For any states s and t if P s and ⦅ c , s ⦆ ⇒ t then Q t
or in symbols:
∀ s t . P s ∧ ⦅ c , s ⦆ ⇒ t ==> Q t
This is known as the partial correctness criterion, since the pre-condition P s is not
required to imply the termination of the execution of c starting with s.
Now, the notion of a valid triple is semantic in nature, as it relies on the (operational) semantics formalized by the big-step relation; worse than this, it is universally quantified over states, so that proving something about commands solely on the ground of operational semantics involves more abstract reasoning tools than just considering particular executions.
What we need is the power of logic, for which we choose Hoare logic.
Hoare logic is a formal system deriving judgements
we shall write as |- [ P ] c [ Q ], whose derivations are finite trees of triples and
predicate implications. Of such a logic
we shall be able to prove that it is sound, in the sense that
|- [ P ] c [ Q ] ==> |= [ P ] c [ Q ] (Soundness Theorem)
Vice versa we shall prove that, provided that all implications among assertions that are needed in a proof are provable within our system Agda, Hoare logic is also complete:
|= [ P ] c [ Q ] ==> |- [ P ] c [ Q ] (Relative Completeness Theorem)
Toward automation
Using Hoare logic to verify that a program meets its contract, namely some pre and post-conditions, may be awesome. This can be mitigated if we are able to factor out purely logical steps from a proof based on Hoare logic, since all other steps can be patterned after the syntactical structure of the program and hence automated. On the other hand the progress made by automated theorem provers, like the SMT solvers, can be of great help when checking the logical correctness of the verification conditions which are the product of such a factorization.
In the last chapter we shall introduce a variant of IMP such that WHILE commands can be decorated
by assertions that are supposed to be their loop invariants. Of course it is the responsibility of the
programmer to write the proper invariants; to verify that this is actually the case we will show
that it sufficies to prove that the predicate vc C Q, automatically
computable from the post-condition Q and the annotated command C, holds to conclude that if
P implies the predicate pre C Q, that is also computable, then |- [ P ] strip C [ Q ] is
derivable in Hoare logic,
where strip C is just the command obtained from the decorated program C by deleting all
annotations.
References and further readings
The language IMP, its semantics and Hoare logic are treated in depth in [1], together with its denotational semantics, a topic which is not addressed in our text. The same topics, even considering some extensions of the language (there called While) are treated in [2], with a chapter dedicated to some techniques of static analysis.
In spite of its simplicity, the language IMP is Turing complete; indeed a sublanguage of it is considered in [3] in place of Turing machines to introduce the basic results of computability theory.
The book [4] has been the basis of our work in this last part of the book. It is a concise exposition of IMP and Hoare logic (with other topics), where all the definitions and results have been checked using the proof assistant Isabel. The proof assistant and the underlying logic constitute the main differences with the present work. While Isabel is based on Higher Order Logic, which is classical logic at higher types, Agda is an implementation of intuitionistic type theory; moreover Isabel exclusively uses tacticals to prove theorems, and no proof object is produced. On the contrary the enphasis of the present work is on (constructive) proofs, that with respect to [4] have been completely reworked.
- G. Winskel, The Formal Semantics of Programming Languages, The MIT Press
- H. R. Nielson & F. Nielson, Semantics with Applications, Wiley
- A. J. Kfoury, R. N. Moll & R. A. Arbib, A Programming Approach to Computability, Springer
- T. Nipkow & G. Klein, Concrete Semantics, Springer