• No results found

This concludes the chapter on the operational semantics for IMP. In the first part of this chapter, we have defined the abstract syntax of IMP commands, we have defined the semantics of IMP in terms of a big-step operational se-mantics, and we have experimented with the concepts of semantic equivalence and determinism. In the second part of this chapter, we have defined an al-ternative form of operational semantics, namely small-step semantics, and we have proved that this alternative form describes the same behaviours as the big-step semantics. The two forms of semantics have different application trade-offs: big-step semantics were easier to define and understand, small-step semantics let us talk explicitly about intermediate states of execution and about termination.

We have looked at three main proof techniques: derivation trees, rule in-version and rule induction. These three techniques form the basic tool set that will accompany us in the following chapters.

7.4 Summary and Further Reading 91 Operational semantics in its small-step form goes back to Plotkin [70, 71,72], who calls it structural operational semantics. Big-step semantics was popularised by Kahn [46] under the name of natural semantics.

There are many programming language constructs that we left out of IMP.

Some examples that are relevant for imperative and object-oriented languages are the following.

Syntax. For loops, do . . . while loops, the if . . . then command, etc. are just further syntactic forms of the basic commands above. They could either be formalized directly, or they could be transformed into equivalent basic forms by syntactic de-sugaring.

Jumps. The goto construct, although considered harmful [27], is relatively easy to formalize. It merely requires the introduction of some notion of program position, be it as an explicit program counter, or a set of la-bels for jump targets. We will see jumps as part of a machine language formalization inChapter 8.

Blocks and local variables. Like the other constructs they do not add computational power but are an important tool for programmers to achieve data hiding and encapsulation. The main formalization challenge with local variables is their visibility scope. Nielson and Nielson [62] give a good introduction to this topic.

Procedures. Parameters to procedures introduce issues similar to local vari-ables, but they may have additional complexities depending on which call-ing conventions the language implements (call by reference, call by value, call by name, etc.). Recursion is not usually a problem to formalize. Pro-cedures also influence the definition of what a program is: instead of a single command, a program now usually becomes a list or collection of procedures. Nielson and Nielson cover this topic as well [62].

Exceptions. Throwing and catching exceptions is usually reasonably easy to integrate into a language formalization. However, exceptions may inter-act with features like procedures and local variables, because exceptions provide new ways to exit scopes and procedures. The Jinja [48] language formalization is an example of such rich interactions.

Data types, structs, pointers, arrays. Additional types such as fixed size machine words, records, and arrays are easy to include when the corre-sponding high-level concept is available in the theorem prover, but IEEE floating point values for instance may induce an interesting amount of work [24]. The semantics of pointers and references is a largely orthogo-nal issue and can be treated at various levels of detail, from raw bytes [87]

up to multiple heaps separated by type and field names [13].

Objects, classes, methods. Object-oriented features have been the target of a large body of work in the past decade. Objects and methods lead

to a stronger connection between control structures and memory. Which virtual method will be executed, for instance, depends on the type of the object in memory at runtime. It is by now well understood how to formalize them in a theorem prover. For a study on a Java-like language in Isabelle/HOL, see for instance Jinja [48] or Featherweight Java [45].

All of the features above can be formalized in a theorem prover. Many add interesting complications, but the song remains the same. Schirmer [78], for instance, shows an Isabelle formalization of big-step and small-step semantics in the style of this chapter for the generic imperative language Simpl. The formalization includes procedures, blocks, exceptions, and further advanced concepts. With techniques similar to those describedChapter 12, he develops the language to a point where it can directly be used for large-scale program verification.

Exercises

Exercise 7.1. Define a function assigned :: com ⇒ vname set that com-putes the set of variables that are assigned to in a command. Prove that if some variable is not assigned to in a command, then that variable is never modified by the command: [[(c, s) ⇒ t ; x /∈ assigned c]] =⇒ s x = t x.

Exercise 7.2. Define a recursive function skip :: com ⇒ bool that determines if a command behaves like SKIP. Prove skip c =⇒ c ∼ SKIP.

Exercise 7.3. Define a recursive function deskip :: com ⇒ com that elimi-nates as many SKIP s as possible from a command. For example:

deskip (SKIP ;; WHILE b DO (x ::= a ;; SKIP )) = WHILE b DO x ::= a Prove deskip c ∼ c by induction on c. RememberLemma 7.5for the WHILE case.

Exercise 7.4. Define a small-step semantics for the evaluation of arithmetic expressions:

inductive astep :: aexp × state ⇒ aexp ⇒ bool (infix 50) where (V x , s) N (s x ) |

(Plus (N i ) (N j ), s) N (i + j ) |

Complete the definition with two rules for Plus that model a left-to-right evaluation strategy: reduce the first argument with if possible, reduce the second argument with if the first argument is a number.

Prove that each step preserves the value of the expression: (a, s) a0 =⇒ aval a s = aval a0 s. Use the modified induction rule astep.induct

7.4 Summary and Further Reading 93 [split_format (complete)]. Do not use the case idiom but write down explic-itly what you assume and show in each case: fix . . . assume . . . show . . . . Exercise 7.5. Prove or disprove (by giving a counterexample):

 IF And b1 b2 THEN c1 ELSE c2

IF b1 THEN IF b2 THEN c1 ELSE c2 ELSE c2

 WHILE And b1 b2 DO c ∼ WHILE b1 DO WHILE b2 DO c

 WHILE Or b1 b2 DO c ∼ WHILE Or b1 b2 DO c;; WHILE b1DO c where Or b1 b2 = Not (And (Not b1) (Not b2)).

Exercise 7.6. Define a new loop construct DO c WHILE b (where c is executed once before b is tested) in terms of the existing constructs in com : DO c WHILE b = .... Define a recursive translation dewhile :: com ⇒ com that replaces all WHILE b DO c by suitable commands that use DO c WHILE b instead. Prove that your translation preserves the semantics:

dewhile c ∼ c.

Exercise 7.7. Let C :: nat ⇒ com be an infinite sequence of commands and S :: nat ⇒ state an infinite sequence of states such that C 0 = c;; d and ∀ n . (C n , S n ) → (C (Suc n ), S (Suc n )). Prove that either all C n are of the form cn;; d and it is always cn that is reduced, or cn eventually becomes SKIP :

[[C 0 = c;; d ; ∀ n . (C n , S n ) → (C (Suc n ), S (Suc n ))]]

=⇒ (∀ n . ∃ c1 c2.

C n = c1;; d ∧

C (Suc n ) = c2;; d ∧ (c1, S n ) → (c2, S (Suc n ))) ∨ (∃ k . C k = SKIP ;; d )

For the following exercises copy theories Com, Big_Step and Small_Step and modify them as required.

Exercise 7.8. Extend IMP with a REPEAT c UNTIL b command. Adjust the definitions of step and small-step semantics, the proof that the big-step semantics is deterministic and the equivalence proof between the two semantics.

Exercise 7.9. Extend IMP with a new command c1 OR c2 that is a non-deterministic choice: it may execute either c1 or c2. Adjust the definitions of big-step and small-step semantics, prove (c1 OR c2) ∼ (c2 OR c1)and update the equivalence proof between the two semantics.

Exercise 7.10. Extend IMP with exceptions. Add two constructors THROW and TRY c1 CATCH c2 to datatype com. Command THROW throws

an exception. The only command that can catch an exception is TRY c1 CATCH c2: if an exception is thrown by c1, execution continues with c2, otherwise c2is ignored. Adjust the definitions of big-step and small-step se-mantics as follows. The big-step sese-mantics is now of type com × state ⇒ com × state. In a big step (c,s) ⇒ (x ,t ), x can only be SKIP (signalling normal termination) or THROW (signalling that an exception was thrown but not caught). The small-step semantics is of the same type as before. There are two final configurations now, (SKIP , t ) and (THROW , t ). Exceptions propagate upwards until an enclosing handler catches them.

Adjust the equivalence proof between the two semantics such that you obtain cs ⇒ (SKIP ,t ) ←→ cs →∗ (SKIP ,t ) and cs ⇒ (THROW ,t ) ←→

cs →∗ (THROW ,t ). Also revise the proof of (∃ cs0.cs ⇒ cs0) ←→ (∃ cs0. cs →∗ cs0 ∧ final cs0).