15-414 Lecture 11 1 Instructor: Matt Fredrikson Induction and Semantics in Dafny TA: Ryan Wagner Encoding the syntax of Imp Recall the abstract syntax of Imp: a AExp ::= n Z x Var a 1 + a 2 b BExp ::= true false a 1 = a 2 a 1 a 2 b b 1 b 2 c Com ::= skip x := a c 1 ; c 2 if b then c 1 else c 2 while b do c We can encode this in Dafny using inductive datatypes. Intuitively, values of inductive datatypes can be seen as finite trees, where nodes correspond to constructors with zero or more arguments. Constructors can take parameters, and when the type of a parameter is the inductive datatype itself, the corresponding node is internal. We begin by declaring types for variables and values, which we will represent using strings and integers, respectively. The remaining definition defines inductive datatypes for AExp, BExp, and Com. 1 type Var = string 2 type Val = int 3 4 datatype AExp = N( Val ) 5 V( Var ) 6 Plus (AExp, AExp ) 7 datatype BExp = B( bool ) 8 Less (AExp, AExp ) 9 Eq(AExp, AExp ) 10 Not ( BExp ) 11 And ( BExp ) 12 datatype Com = Skip 13 Assign (Var, AExp ) 14 Seq (Com, Com ) 15 If(BExp, Com, Com ) 16 While (BExp, Com ) To see how we might use these types, consider the expression (5 + x) < y: 1 var b := Less ( Plus (N (5), V("x")), V("y")); 1
15-414 Lecture 11 2 To encode a simple program: We would construct the type: while (5 + x) < y do x := x + 1 1 var b := Less ( Plus (N (5), V("x")), V("y")); 2 var c := While (b, Assign ("x", Plus (V("x"), N (1) ))); Inference Rules Now let s implement the big-step semantics. Recall that our semantics defined an environment as a mapping from variables to their values, and a configuration as a pair consisting of a command and an environment. Additionally, we discussed derivations, which we can model as a finite sequence of configurations. 1 type Env = map < Var, Val > 2 type Config = ( Com, Env ) 3 type Derivation = seq < Config > Let s start implementing the inference rules themselves by covering the rules for the expressions. Const n, σ a n Var x, σ a σ(x) Add a 1, σ a n 1 a 2, σ a n 2 a 1 + a 2, σ a n 1 + Z n 2 We know that evaluating expressions always terminates, so we can model these rules with a single function. We ll use a match expression to split into rules based on the abstract syntax of the expression passed in. 1 function bigstep_ aexp ( a: AExp, s: Env ): Val 2 { 3 match (a) { 4 case N( n) => n 5 case V( x) => if x in s then s[ x] else 0 6 case Plus (a0, a1) => bigstep_aexp (a0, s) + bigstep_aexp (a1, s) 7 } 8 } The only aspect of this function that may be unexpected is the rule for variable evaluation. Previously, we ve assumed that the environment will have a mapping for every possible variable, but we made no assumptions about what the mapping could be. Here we first check to see that the referenced variable is in the environment, and if it isn t, the function returns zero. This introduces an additional assumption that we haven t made before, which is that all variables start off initialized to zero. Alternatively, we could have written the function with a precondition that requires any environment passed in to contain all mappings for all variables mentioned in the expression: 2
15-414 Lecture 11 3 1 function bigstep_ aexp ( a: AExp, s: Env ): Val 2 requires forall v: Var :: v in free_ vars ( a) == > v in s 3 { 4 match (a) { 5 case N( n) => n 6 case V( x) => if x in s then s[ x] else 0 7 case Plus (a0, a1) => bigstep_aexp (a0, s) + bigstep_aexp (a1, s) 8 } 9 } We would then need to define a function free vars, and whenever we called bigstep aexp, we d need to make sure that this precondition were met. To simplify things today, we ll use the former approach, and assume everything starts out initialized to zero. Moving on to the rules for BExp, we have the following rules. True true, σ b true False false, σ b false Eq a 1, σ a n 1 a 2, σ a n 2 a 1 = a 2, σ a n 1 = Z n 2 Less a 1, σ a n 1 a 2, σ a n 2 a 1 < a 2, σ a n 1 < Z n 2 NotTrue b, σ b true b, σ b false NotFalse b, σ b false b, σ b true And b 1, σ b v 1 b 2, σ b v 2 b 1 b 2, σ b v 1 v 2 Using the same reasoning we did for the arithmetic expressions, we arrive at the following function. There are no surprises here, we use the same approach as before. 1 function bigstep_ bexp ( b: BExp, s: Env ): bool 2 { 3 match (b) { 4 case B( v) => v 5 case Less (a0, a1) => bigstep_aexp (a0, s) < bigstep_aexp (a1, s) 6 case Eq(a0, a1) => bigstep_aexp (a0, s) == bigstep_aexp (a1, s) 7 case Not (op) =>! bigstep_bexp (op, s) 8 case And (b0, b1) => bigstep_bexp (b0, s) && bigstep_bexp (b1, s) 9 } 10 } 3
15-414 Lecture 11 4 Recall the rules for big-step evaluation of commands: Asgn a, σ a n x := a, σ σ[x n] Skip skip, σ σ Seq c 1, σ 1 σ 1 c 2, σ 1 σ 2 c 1 ; c 2, σ 1 σ 2 IfTrue b, σ b true c 1, σ σ 2 if b then c 1 else c 2, σ σ 2 IfFalse b, σ b false c 2, σ σ 2 if b then c 1 else c 2, σ σ 2 WhileFalse b, σ b false while b do c, σ σ WhileTrue b, σ b true c, σ σ while b do c, σ σ while b do c, σ σ If we were to implement these in the same way that we did for expressions, we would run into trouble with termination. Unlike expressions, commands need not terminate, so the function that we wrote would not be defined on certain inputs. Instead, we ll define the semantics for commands using a predicate, which you ll recall is just a Boolean-valued function. The predicate will take as arguments a command c, environments s and s, and a natural number k. We want the predicate to return true whenever evaluating c starting in s reduces to s with a derivation containing at most k steps. 1 predicate bigstep ( c: Com, s: Env, s : Env, k: nat ) 2 decreases c,k 3 { 4 match (c) { 5 case Skip => 6 k == 1 && s == s 7 case Assign (v, a) => 8 k == 1 && s[ v := bigstep_ aexp (a, s)] == s 9 case Seq (c1, c2) => 10 exists k : nat, s : Env :: 11 0 < k < k && 12 bigstep (c1, s, s, k ) && 13 bigstep (c2, s, s, k-k ) 14 case If(b, c1, c2) => 15 if( bigstep_bexp (b, s)) then 16 bigstep (c1, s, s, k) 17 else 18 bigstep (c2, s, s, k) 19 case While (b, c ) => 20 if( bigstep_bexp (b, s)) then 21 exists s : Env, k : nat :: 0 < k < k && 22 bigstep (c, s, s, k ) && 23 bigstep (c, s, s, k-k ) 24 else 25 k == 1 && s == s 26 } 27 } Unlike our functions expression semantics, this function does not have a case for each rule. Instead, it has a case for each syntactic form of Com, and composes recursive calls in a way that faithfully 4
15-414 Lecture 11 5 accounts for each rule. Notice that our termination metric refers to two variables, c and k. This is necessary because some cases only reduce the size of the expression (e.g., if), and some only reduce the size of the derivation (e.g., while) when they recursively call the semantics. Using the semantics Proving facts about specific programs Now that we have an implementation of the semantics, we can get started applying them to reason about Imp programs. First we ll take a look at proving things about specific programs given as Com types. Suppose that we re given the program: while 0 < x do x := x 1 and we d like to show that staring out in an environment σ = [x 2] leads to a final environment σ = [x 0]. Although this might be obvious to us by simple inspection of the program, to prove that this is the case, we ll need to make use of the semantics. Formally, we d like to show that, while 0 < x do x := x 1, [x 2] [x 0] Before seeing how to do this using Dafny, let s review how to do it on paper first. Recall that we can prove specific derivations by finding a proof tree that applies the relevant inference rules for our judgement. In this case, we know that the last rule applied in such a tree will be WhileTrue, because the command is a while loop and in the environment σ = [x 2], 0 < x, σ b true. To the root of the proof tree will look as follows: WhileTrue T 1 T 2 T 3 while 0 < x do x := x 1, [x 2] [x 0] By inspecting the rule WhileTrue, we know that T 1 will need to end with the conclusion 0 < x, [x 2] b true, T 2 will need to end with the conclusion x := x 1, [x 2] σ, and T 3 will need to end with while 0 < x do x := x 1, σ [x 0]. To finish the proof, we need to construct T 1, T 2, T 3. We now need to derive the tree T 1, which is an inequality comparison. We see that: Less Const 0, [x 2] a 0 0 < x, [x 2] b true Var x, [x 2] a 2 Moving on to T 2, which is an assignment, we need to apply Asgn. By inspecting the command and inital environment, we know that σ, the final environment, must be equal to [x 1]. We justify this as follows: Asgn Plus Var Const x, [x 2] a 2 1, [x 2] a 1 x 1, [x 2] a 1 x := x 1, [x 2] [x 1] 5
15-414 Lecture 11 6 Moving on to T 3, we now know that σ = [x 1]. Again, we re looking at a loop, and we know that 0 < x, [x 1] b true, so T 3 will be rooted by WhileTrue. WhileTrue T 4 T 5 T 6 while 0 < x do x := x 1, [x 1] [x 0] We re going to re-use much of our work from T 1, T 2, and T 3, keeping in mind that the initial environment has changed to [x 1]. Starting with T 4 : Less Const 0, [x 1] a 0 0 < x, [x 1] b true Var x, [x 1] a 1 Continuing with T 5, we find that the next environment is [x 0]: Asgn Plus Var Const x, [x 1] a 1 1, [x 1] a 1 x 1, [x 1] a 0 x := x 1, [x 1] [x 0] Now that we get to T 6, we have to re-consider the while loop. We won t apply WhileTrue this time, because the initial environment maps x to 0. Now we can apply WhileFalse: Asgn Const Var 0, [x 0] a 0 0, [x 0] a 0 Less 0 < x, [x 0] b false while 0 < x do x := x 1, [x 0] [x 0] This completes the proof, because there are no further assumptions that need to be discharged. Now we ll see how to do this in Dafny. We prove things about functions and predicates in Dafny using lemmas. You can think of lemmas as methods that are not meant to be compiled, so their utility draws from the specifications that we give them. Dafny lemmas can be later invoked to prove to the verifier that a particular property holds for some objects. For the present example, recall that we want to show that, while 0 < x do x := x 1, [x 2] [x 0] In our implementation, bigstep represents the relation, and we represent environments as maps, so [x 2] is represented by map["x" := 2] and [x 0] by map["x" := 0]. The program that we re reasoning about is a value of type Com, which we can write as: 1 While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))) Additionally, from our earlier proof, we know that this program evaluates on its initial environment in three steps (including the exit from the loop). Putting this together, our lemma will start out with: 6
15-414 Lecture 11 7 1 lemma loop_ to_ zero () 2 ensures bigstep ( While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))), 3 map ["x" := 2], 4 map ["x" := 0], 5 3) Notice that we use the postcondition of the lemma, specified using the ensures syntax, to state the property that we re trying to prove. If we had any assumptions or premises to work with, i.e., if we were proving a statement of the form F G, then we could have used a requires annotation to specify this in our lemma. We ll now write the proof of this property in the body of the lemma, much as we d write a method. But we ll start out by defining some variables as shorthand for the main components of our property: 1 var c := While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))); 2 var b := Less (N (0), V("x")); 3 var body := Assign ("x", Plus (V("x"), N( -1))); 4 var a := Plus (V("x"), N( -1)); 5 var s := map ["x" := 2]; 6 var s := map ["x" := 0]; We re now ready to begin proving the lemma. To prove this property, we need to show that the rules, which we encoded in the predicate bigstep, give us a way to reach our desired conclusion. Our lemma has no requires annotations, so we don t have any premises to start out with other than true. This means we we need to show that: true ( while 0 < x do x := x 1, [x 2] [x 0]) To do this, we ll use a calculational proof. You ve seen these before, but probably not named as such: starting from an initial premise (in our case, true), we ll provide a chain of implications that leads to the desired conclusion. You ve probably written proofs of the form: P F 1. Q Dafny has a special syntax for calculational proofs, which looks like the following: 1 calc == > { 2 P; 3 { justification } 4 F1; 5 { justification } 6... 7 { justification } 8 Q; 9 } The symbol ==> tells Dafny that we re going to show a sequence of right-implications. The justifications are optional when Dafny is able to prove that each step follows from its predecessor, but if this is not the case, then you may need to fill these in with appropriate assert statements or call existing lemmas to help Dafny prove your claim. Calculational proofs in Dafny are somewhat generic, so we could have also used the symbol <==, which would reverse the chain using left-implications to work from the conclusion back to the 7
15-414 Lecture 11 8 premise: 1 calc <== { 2 Q 3 { justification } 4 Fn 5 { justification } 6... 7 { justification } 8 P 9 } When we worked out the proof for the current example by hand, we actually proceeded backwards from the conclusion to applications of axioms. We ll follow that example when we work this in Dafny, making use of calc <== to do so. Starting with the conclusion bigstep(c, s, s, 3), which corresponds to the root of our proof tree from before, we know that x, [x 2] 2, so the head of the loop (0 < x) evaluates to true. We also know that to reach this conclusion, we ll need to look at the part of the semantics that deals with while loops: 1 case While (b, c ) => 2 if( bigstep_bexp (b, s)) then 3 exists s : Env, k : nat :: 0 < k < k && 4 bigstep (c, s, s, k ) && 5 bigstep (c, s, s, k-k ) 6 else 7 k == 1 && s == s In particular, the branch of the if expression corresponding to the positive evaluation of the guard b. This says that to show big-step evaluation from s to s in 3 steps, we need to show the existence of an intermediate state s and natural number k that takes the body from s to s in k steps, and then takes the rest of the loop from s to s in k-k steps. Because the body subtracts 1 from x, and x takes the value 2 in s, we know that the intermediate state must be s["x" := 1]. Furthermore, the rule for assignment consumes 1 step, so we know that k must be 1. This lets us write: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 } Although Dafny doesn t complain about any of the steps in our calculation, we re not done yet, as Dafny still doesn t agree that the lemma s postcondition is satisfied. This means that we need to justify the last line of our calculation. In particular, we need to show that evaluating the loop body starting in s leads to a new state where x takes the value 1, and why it s safe to assume that executing c in that new state leads to s in 2 steps. We get the former from the semantics of the plus operator (which, recall, is implemented in bigstep aexp), and the latter we ll need to justify in subsequent steps: 8
15-414 Lecture 11 9 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 // def. of plus, def. of while 8 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 9 } Again, we need to prove the last line, as Dafny cannot do it on its own. We ll begin with the second conjunct, as it is the more difficult of the two. Using reasoning similar to what we did for the second line, we can write: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 // def. of plus, def. of while 8 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 9 // def. of while 10 bigstep_bexp (b, s["x" := 1]) && 11 bigstep (body, s["x" := 1], s, 1) && 12 bigstep (c, s, s, 1); 13 } Again, we used the true case of the definition of semantics for while to derive this step. We know that executing the body x := x 1 in the environment [x 1] leads to [x 0], which is the environment s mentioned in our goal. Notice that Dafny accepts this line in the calculational proof without asking for justification of the conjunct bigstep aexp(a, s) == 1. This means that we don t need to prove it Dafny can do so on its own, in contrast to our by-hand proof, where completeness required a worked-out justification. Continuing just as before, Dafny isn t able to prove that evaluating the body results in the postenvironment s. Previously, we dealt with this by telling Dafny that bigstep aexp(a, s) == 1. In this case, we point out that bigstep aexp(a, s["x" := 1]) == 0: 9
15-414 Lecture 11 10 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 { assert bigstep_ aexp (a, s) == 1; } 8 // def. of plus, def. of while 9 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 10 // def. of while 11 bigstep_bexp (b, s["x" := 1]) && 12 bigstep (body, s["x" := 1], s, 1) && 13 bigstep (c, s, s, 1); 14 // def. of assign, seq 15 bigstep_ aexp (a, s[" x" := 1]) == 0; 16 } At this point, it seems like we ve given Dafny everything it needs to know, but it still thinks we re not done, as it tells us that the lemma postcondition might not hold. Let c = while 0 < x do x := x 1. Looking at the body of the lemma, we ve just shown that: x 1, [x 1] a 0 0 < x, [x 1] b true x := x 1, [x 1] [x 0] c, [x 0] a [x 0] c, [x 1] [x 0] 0 < x, [x 2] b true x := x 1, [x 2] [x 1] c, [x 1] a [x 0] c, [x 2] [x 0] The conclusion of this calculation corresponds to the postcondition of our lemma. However, Dafny can only use it to conclude that the postcondition holds if it believes that the premise is true. We can point this out by adding a corresponding assertion after the calculation: 1 calc <== { 2 bigstep (c, s, s, 3); 3 // def. of while 4 bigstep_ bexp (b, s) && 5 bigstep (body, s, s["x" := 1], 1) && 6 bigstep (c, s["x" := 1], s, 2); 7 { assert bigstep_ aexp (a, s) == 1; } 8 // def. of plus, def. of while 9 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 10 // def. of while 11 bigstep_bexp (b, s["x" := 1]) && 12 bigstep (body, s["x" := 1], s, 1) && 13 bigstep (c, s, s, 1); 14 // def. of assign, seq 15 bigstep_ aexp (a, s[" x" := 1]) == 0; 16 } 17 assert bigstep_ aexp (a, s[" x" := 1]) == 0; Alternatively, we could add another line to the calculation that Dafny will certainly believe to be true, i.e., true. This should force it to attempt a proof that the first real line of the calculation is valid. Putting all of this together, the full lemma becomes: 10
15-414 Lecture 11 11 1 lemma loop_ to_ zero () 2 ensures bigstep ( While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))), 3 map ["x" := 2], 4 map ["x" := 0], 5 3) 6 { 7 var c := While ( Less (N (0), V("x")), Assign ("x", Plus (V("x"), N( -1)))); 8 var b := Less (N (0), V("x")); 9 var body := Assign ("x", Plus (V("x"), N( -1))); 10 var a := Plus (V("x"), N( -1)); 11 var s := map ["x" := 2]; 12 var s := map ["x" := 0]; 13 14 calc <== { 15 bigstep (c, s, s, 3); 16 // def. of while 17 bigstep_ bexp (b, s) && 18 bigstep (body, s, s["x" := 1], 1) && 19 bigstep (c, s["x" := 1], s, 2); 20 { assert bigstep_ aexp (a, s) == 1; } 21 // def. of plus, def. of while 22 bigstep_aexp (a, s) == 1 && bigstep (c, s["x" := 1], s, 2); 23 // def. of while 24 bigstep_bexp (b, s["x" := 1]) && 25 bigstep (body, s["x" := 1], s, 1) && 26 bigstep (c, s, s, 1); 27 // def. of assign, seq 28 bigstep_ aexp (a, s[" x" := 1]) == 0; 29 true ; 30 } 31 } Proving semantic properties Now let s return to the fact that the big-step semantics for Imp are deterministic, which we discussed in lecture. Recall that we proved the property: σ, σ 1, σ 2, c.( c, σ σ 1 c, σ σ 2 ) σ 1 = σ 2 In other words, for a given command, if we begin evaluating in any environment, then the environment we end with (if any) is unique. We proved this by induction on derivations, because induction on syntax is not well-founded. To prove this using our Dafny semantics, we ll begin by writing the lemma: 1 lemma bigstep_determ (c: Com, s0: Env, s1: Env, s1 : Env, k1: nat, k2: nat ) 2 requires bigstep (c, s0, s1, k1) 3 requires bigstep (c, s0, s1, k2) 4 ensures s1 == s1 5 ensures k1 == k2 There are several differences between this lemma from the previous one. First of all, bigstep determ takes arguments. In particular, a subset of its arguments correspond to the universally-quantified variables σ, σ 1, σ 2, and c from the statement we re trying to prove. When lemmas take arguments 11
15-414 Lecture 11 12 that are mentioned in their postconditions, they have to prove that the postcondition holds for whatever values are passed in as long as they satisfy the lemma s preconditions, which is like making a universal statement. Second, bigstep determ has two requires annotations, which together correspond to the antecedent of our proof goal. The last two arguments to bigstep determ correspond to the length of the derivations that we assume to exist. As we will see, including these as arguments gives us a well-founded induction later on. Alternatively, we could have defined a predicate: 1 predicate bigstep_ deriv ( c: Com, d: Derivation ) We could define bigstep big-step semantics, i.e., deriv to return true whenever d is a valid derivation of c under the 1 d [0].0 == c && 2 forall i :: 0 <= i < d -1 == > bigstep (d[i].0, d[i].1, d[i +1].1, d -i) We might then have written the lemma: 1 lemma bigstep_ determ ( c: Com, d: Derivation, d : Derivation ) 2 requires 0 < d && 0 < d 3 requires c == d [0].0 && c == d [0].0 4 requires bigstep_ deriv (c, d) 5 requires bigstep_ deriv (c, d ) 6 ensures d[ d -1].1 == d [ d -1].1 7 ensures d == d However, to avoid introducing new predicates and functions, we ll continue with the proof using our existing definitions. We ll proceed by induction, using a match on c to guide our coverage of the possible derivations. Starting with the base case corresponding to skip: 1 match (c) { 2 case Skip => 3 assert s0 == s1 == s1 ; Moving to assignment, we add the case: 1 case Assign (v, a) => 2 assert s0[ v := bigstep_ aexp (a, s0)] == s1 == s1 ; Notice that Dafny accepts this assertion. In lecture, we needed to prove that evaluation of expressions is deterministic. Dafny can deduce this automatically, which you can check by writing the lemma that verifies without assistance: 1 lemma aexp_determ (a: AExp, s: Env, n1: Val, n2: Val ) 2 requires bigstep_ aexp (a, s) == n1 3 requires bigstep_ aexp (a, s) == n2 4 ensures n1 == n2 5 {} The case for Seq is a bit more involved. Because we can assume that c 1 ; c 2, σ 0 σ 1, there must 12
15-414 Lecture 11 13 be a derivation that looks like: T 1 T 2 Seq c 1, σ 0 σ 0 c 2, σ 0 σ 1 c 1 ; c 2, σ 0 σ 1 Similarly for the second evaluation: Seq T 3 c 1, σ 0 σ 0 c 1 ; c 2, σ 0 σ 1 T 4 c 2, σ 0 σ 1 We can apply the inductive hypothesis on T 1, T 2, T 3, and T 4 to conclude that σ 0 = σ 0 σ 1 = σ 1. To write this in Dafny, we say the following: 1 case Seq (c1, c2) => 2 var s0, k1 : 0 < k1 < k1 && 3 bigstep (c1, s0, s0, k1 ) && 4 bigstep (c2, s0, s1, k1 -k1 ); 5 var s0, k2 : 0 < k2 < k2 && 6 bigstep (c1, s0, s0, k2 ) && 7 bigstep (c2, s0, s1, k2 -k2 ); 8 bigstep_determ (c1, s0, s0, s0, k1, k2 ); 9 assert s0 == s0 ; 10 bigstep_determ (c2, s0, s1, s1, k1 -k1, k2 -k2 ); 11 assert s1 == s1 ; We use some new syntax here, namely the assign-such-that statement: 1 var x : p( x) and thus If p is a predicate, then you should think of this statement as returning an arbitrary value for x, such that it satisfies p(x). We use this to reason about the intermediate environments s0 (corresponding to σ 0 ) and s0 (corresponding to σ 0 ), as well as the step indices k1 and k2 needed by bigstep. In order to use the assign-such-that statement, Dafny must be able to determine that at least one value satisfying the right hand side exists. In this case, we know that such values exist because we know that bigstep(c,s0,s1,k1) and bigstep(c,s0,s1,k2) are true, and the semantic definition for Seq is: 1 case Seq (c1, c2) => 2 exists k : nat, s : Env :: 3 0 < k < k && bigstep (c1, s, s, k ) && bigstep (c2, s, s, k-k ) The next part of the proof for Seq also uses a new concept. Namely, it calls the lemma recursively: 1 bigstep_determ (c1, s0, s0, s0, k1, k2 ); Important: This is how we apply the inductive hypothesis in Dafny. Notice that we re applying this to smaller derivations than the ones our current preconditions give us: we know that 0 < k1 < k1 and 0 < k2 < k2. By making this call on smaller arguments, the postcondition given by the lemma is now available to Dafny as an assertion would be. The following line that asserts s0 == s0 points this out for the reader s benefit, but is not strictly necessary; however, it is considered 13
15-414 Lecture 11 14 good form in this course to make your proofs explicit and easy to follow. We close out the case for Seq by making another inductive call on the second evaluation. The next case covers if commands. Like the previous case, it makes an inductive call for each branch of the statment: 1 case If(b, c1, c2) => 2 if( bigstep_bexp (b, s0)) { 3 bigstep_determ (c1, s0, s1, s1, k1, k2); 4 assert s1 == s1 ; 5 } else { 6 bigstep_determ (c2, s0, s1, s1, k1, k2); 7 assert s1 == s1 ; 8 } Finally, we come to while. As we ve discussed in lecture, semantically a while loop is like an if command composed with a seq. In lecture we said that in this case we could reason as follows. If the guard evaluates to true, then we can assume derivations: WhileTrue T 1 b, σ 0 b true T 2 T 3 c, σ 0 σ 0 while b do c, σ 0 σ 1 while b do c, σ 0 σ 1 WhileTrue T 4 b, σ 0 b true T 5 T 6 c, σ 0 σ 0 while b do c, σ 0 σ 1 while b do c, σ 0 σ 2 Applying the inductive hypothesis twice leads us to conclude that σ 0 = σ 0 and σ 1 = σ 1. As this is very similar to the case for Seq, our proof in Dafny follows form: 1 case While (b, c ) => 2 if( bigstep_bexp (b, s0)) { 3 var s0, k1 : 0 < k1 < k1 && 4 bigstep (c, s0, s0, k1 ) && 5 bigstep (c, s0, s1, k1 -k1 ); 6 var s0, k2 : 0 < k2 < k2 && 7 bigstep (c, s0, s0, k2 ) && 8 bigstep (c, s0, s1, k2 -k2 ); 9 bigstep_determ (c, s0, s0, s0, k1, k2 ); 10 assert s0 == s0 ; 11 bigstep_determ (c, s0, s1, s1, k1 -k1, k2 -k2 ); 12 assert s1 == s1 ; We use assign-such-that, with a such-that condition coming directly from the big-step definition of while, to obtain intermediate states s0, s0 and step indices k1, k2. We then apply the inductive hypothesis twice by making two recursive calls to the lemma, and conclude the WhileTrue case. WhileFalse is straigtforward, as it has the same semantics as skip. 1 } else { 2 assert s0 == s1 == s1 ; 3 } Although we ve completed all of the relevant cases, Dafny does not accept our proof. The problem is well-foundedness, which requires providing a termination metric that the verifier will understand. 14
15-414 Lecture 11 15 For this proof, we need a compound termination metric, as one case fails to decrease the step indices k1, k2 (namely, the case for If), and another fails to decrease the command (i.e., While). We combine all three for our termination metric, on which Dafny will impose a lexicographic ordering. Putting all this together, we have the following lemma with proof: 1 lemma bigstep_determ (c: Com, s0: Env, s1: Env, s1 : Env, k1: nat, k2: nat ) 2 decreases c, k1, k2 3 requires bigstep (c, s0, s1, k1) 4 requires bigstep (c, s0, s1, k2) 5 ensures s1 == s1 6 ensures k1 == k2 7 { 8 match (c) { 9 case Skip => 10 assert s0 == s1 == s1 ; 11 case Assign (v, a) => 12 assert s0[ v := bigstep_ aexp (a, s0)] == s1 == s1 ; 13 case Seq (c1, c2) => 14 var s0, k1 : 0 < k1 < k1 && 15 bigstep (c1, s0, s0, k1 ) && 16 bigstep (c2, s0, s1, k1 -k1 ); 17 var s0, k2 : 0 < k2 < k2 && 18 bigstep (c1, s0, s0, k2 ) && 19 bigstep (c2, s0, s1, k2 -k2 ); 20 bigstep_determ (c1, s0, s0, s0, k1, k2 ); 21 assert s0 == s0 ; 22 bigstep_determ (c2, s0, s1, s1, k1 -k1, k2 -k2 ); 23 assert s1 == s1 ; 24 case If(b, c1, c2) => 25 if( bigstep_bexp (b, s0)) { 26 bigstep_determ (c1, s0, s1, s1, k1, k2); 27 assert s1 == s1 ; 28 } else { 29 bigstep_determ (c2, s0, s1, s1, k1, k2); 30 assert s1 == s1 ; 31 } 32 case While (b, c ) => 33 if( bigstep_bexp (b, s0)) { 34 var s0, k1 : 0 < k1 < k1 && 35 bigstep (c, s0, s0, k1 ) && 36 bigstep (c, s0, s1, k1 -k1 ); 37 var s0, k2 : 0 < k2 < k2 && 38 bigstep (c, s0, s0, k2 ) && 39 bigstep (c, s0, s1, k2 -k2 ); 40 bigstep_determ (c, s0, s0, s0, k1, k2 ); 41 assert s0 == s0 ; 42 bigstep_determ (c, s0, s1, s1, k1 -k1, k2 -k2 ); 43 assert s1 == s1 ; 44 } else { 45 assert s0 == s1 == s1 ; 46 } 47 } 48 } 15