Lecture notes 20190416

Small Step Semantics 2

Require Import PL.Imp6.
Require Import Coq.Relations.Relation_Operators.
Require Import Coq.Relations.Relation_Definitions.

Arguments clos_refl_trans {A} _ _ _.
Arguments clos_refl_trans_1n {A} _ _ _.
Arguments clos_refl_trans_n1 {A} _ _ _.

Review: Small Step Semantics for Expression Evaluation


Inductive aexp_halt: aexpProp :=
  | AH_num : n, aexp_halt (ANum n).

Inductive astep : stateaexpaexpProp :=
  | AS_Id : st X,
      astep st
        (AId X) (ANum (st X))

  | AS_Plus1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (APlus a1 a2) (APlus a1' a2)
  | AS_Plus2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (APlus a1 a2) (APlus a1 a2')
  | AS_Plus : st n1 n2,
      astep st
        (APlus (ANum n1) (ANum n2)) (ANum (n1 + n2))

  | AS_Minus1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (AMinus a1 a2) (AMinus a1' a2)
  | AS_Minus2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (AMinus a1 a2) (AMinus a1 a2')
  | AS_Minus : st n1 n2,
      astep st
        (AMinus (ANum n1) (ANum n2)) (ANum (n1 - n2))

  | AS_Mult1 : st a1 a1' a2,
      astep st
        a1 a1'
      astep st
        (AMult a1 a2) (AMult a1' a2)
  | AS_Mult2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      astep st
        (AMult a1 a2) (AMult a1 a2')
  | AS_Mult : st n1 n2,
      astep st
        (AMult (ANum n1) (ANum n2)) (ANum (n1 * n2)).

Inductive bexp_halt: bexpProp :=
  | BH_True : bexp_halt BTrue
  | BH_False : bexp_halt BFalse.

Inductive bstep : statebexpbexpProp :=

  | BS_Eq1 : st a1 a1' a2,
      astep st
        a1 a1'
      bstep st
        (BEq a1 a2) (BEq a1' a2)
  | BS_Eq2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      bstep st
        (BEq a1 a2) (BEq a1 a2')
  | BS_Eq_True : st n1 n2,
      n1 = n2
      bstep st
        (BEq (ANum n1) (ANum n2)) BTrue
  | BS_Eq_False : st n1 n2,
      n1n2
      bstep st
        (BEq (ANum n1) (ANum n2)) BFalse


  | BS_Le1 : st a1 a1' a2,
      astep st
        a1 a1'
      bstep st
        (BLe a1 a2) (BLe a1' a2)
  | BS_Le2 : st a1 a2 a2',
      aexp_halt a1
      astep st
        a2 a2'
      bstep st
        (BLe a1 a2) (BLe a1 a2')
  | BS_Le_True : st n1 n2,
      n1n2
      bstep st
        (BLe (ANum n1) (ANum n2)) BTrue
  | BS_Le_False : st n1 n2,
      n1 > n2
      bstep st
        (BLe (ANum n1) (ANum n2)) BFalse

  | BS_NotStep : st b1 b1',
      bstep st
        b1 b1'
      bstep st
        (BNot b1) (BNot b1')
  | BS_NotTrue : st,
      bstep st
        (BNot BTrue) BFalse
  | BS_NotFalse : st,
      bstep st
        (BNot BFalse) BTrue

  | BS_AndStep : st b1 b1' b2,
      bstep st
        b1 b1'
      bstep st
       (BAnd b1 b2) (BAnd b1' b2)
  | BS_AndTrue : st b,
      bstep st
       (BAnd BTrue b) b
  | BS_AndFalse : st b,
      bstep st
       (BAnd BFalse b) BFalse.

Multi-step Relation

We can now use the single-step relations and their reflexive, transitive closure to formalize an entire execution.
Definition multi_astep (st: state): aexpaexpProp := clos_refl_trans (astep st).

Definition multi_bstep (st: state): bexpbexpProp := clos_refl_trans (bstep st).
Intuitively, if multi_astep st a1 a2 is true, then a2 is an intermidiate result of evaluating a1 on st. And multi_bstep st b1 b2 says that b2 is an intermidiate result of evaluating b1 on st. Here is several examples.
Module Example1.

Import Abstract_Pretty_Printing.

Example multi_step_ex1: (Y: var) (st: state),
  st Y = 2 →
  multi_astep st (1 + (3 +Y)) (1 + 5).
Proof.
  intros.
  eapply rt_trans.
  + apply rt_step.
    apply AS_Plus2.
    { apply AH_num. }
    apply AS_Plus2.
    { apply AH_num. }
    apply AS_Id.
  + rewrite H.
    apply rt_step.
    apply AS_Plus2.
    { apply AH_num. }
    apply AS_Plus.
Qed.

Example multi_step_ex2: (X: var) (st: state),
  st X = 1 →
  multi_bstep st ((X ≤ 0) && (0 ≤ X + 10)) BFalse.
Proof.
  intros.
  eapply rt_trans.
  { apply rt_step. apply BS_AndStep. apply BS_Le1. apply AS_Id. }
  rewrite H.
  eapply rt_trans.
  { apply rt_step. apply BS_AndStep. apply BS_Le_False. omega. }
  { apply rt_step. apply BS_AndFalse. }
Qed.

Example multi_step_ex3: (X: var) (st: state),
  st X = 1 →
  multi_bstep st ((X ≤ 0) && (0 ≤ X + 10)) ((X ≤ 0) && (0 ≤ X + 10)).
Proof.
  intros.
  apply rt_refl.
Qed.

End Example1.
The multi-step relations satisfy the congruence property. For example,
Theorem multi_congr_APlus1: st a1 a1' a2,
  multi_astep st a1 a1'
  multi_astep st (a1 + a2) (a1' + a2).
Proof.
Why? Because for every single step from a1 to a1', we can construct a corresponding step from a1 + a2 to a1' + a2.
  intros.
  unfold multi_astep in H.
  unfold multi_astep.
  apply Operators_Properties.clos_rt_rtn1_iff.
  apply Operators_Properties.clos_rt_rtn1_iff in H.
  induction H.
  + apply rtn1_refl.
  + eapply rtn1_trans.
    - apply AS_Plus1.
      exact H.
    - apply IHclos_refl_trans_n1.
Qed.
Remark: we could complete this proof using clos_refl_trans instead of clos_refl_trans_n1. We pick this choice just for convenience.
Theorem multi_congr_APlus2: st a1 a2 a2',
  aexp_halt a1
  multi_astep st a2 a2'
  multi_astep st (a1 + a2) (a1 + a2').
Proof.
It is critical that we require a1 to be a constant.
  intros.
  unfold multi_astep in H.
  unfold multi_astep.
  apply Operators_Properties.clos_rt_rtn1_iff.
  apply Operators_Properties.clos_rt_rtn1_iff in H0.
  induction H0.
  + apply rtn1_refl.
  + eapply rtn1_trans.
    - apply AS_Plus2.
Here we go. Without the assumption H: aexp_halt a1, we cannot complete our proof here.
      * exact H.
      * exact H0.
    - apply IHclos_refl_trans_n1.
Qed.
We can prove similar properties for AMinus, AMult, BEq, BLe, BNot and BAnd.

Small Step Semantics for Simple Imperative Programes

The semantics of commands is more interesting. For expression evaluation, the program states are stable. In other words, we only talked about how an expression will be reduced to another one on a fixed program state. But for program execution, the program states are modified. Thus, a ternary relation among the program state, the command to execute and the residue command after one step is not expressive enough to describe programs' behavior. A quaternary relation is needed.
Specifically, we would like to define a predicate cstep such that
    cstep st1 c1 st2 c2
says if the program to execute is c1, the taking one step forward on state st1 may end in state st2 while the residue command is c2.
Traditionally, computer scientist will describe their theory in a slightly different form. They usually treat cstep as a binary relation between state-command pairs and use the following notation:
    (st1c1--> (st2c2).
We will follow this style in our course, i.e. we will write
    cstep (st1c1) (st2c2).

Coq Data Type: Pairs


Module Playground.
In Coq, product types are defined as the follow polymorphic inductive type. This declaration can be read: "There is just one way to construct an A-B pair: by applying the constructor pair to two arguments of type A and B."
Inductive prod (A B : Type) : Type :=
| pair (a : A) (b : B).

Arguments pair {A} {B} _ _.

Notation "( a , b )" := (pair a b).

Notation "A * B" := (prod A B) : type_scope.

Check (pair 3 5).
Here are simple functions for extracting the first and second components of a pair.
Definition fst {A B: Type} (p : A * B) : A :=
  match p with
  | pair x yx
  end.

Definition snd {A B: Type} (p : A * B) : B :=
  match p with
  | pair x yy
  end.

Example fst_example:
  fst (3, 5) = 3.
Proof. reflexivity. Qed.
Note that the pair notation can also be used in pattern matching.
Definition swap_pair {A B: Type} (p : A * B): B * A :=
  match p with
  | (x,y) ⇒ (y,x)
  end.
Let's try to prove a few simple facts about pairs. If we state things in a slightly peculiar way, we can complete proofs with just reflexivity (and its built-in simplification):
Theorem surjective_pairing' : (n m : Z),
  (n,m) = (fst (n,m), snd (n,m)).
Proof. intros. reflexivity. Qed.
Here, we can see the effect of simpl.
Theorem surjective_pairing'_alter : (n m : Z),
  (n,m) = (fst (n,m), snd (n,m)).
Proof. intros. simpl. reflexivity. Qed.
But reflexivity is not enough if we state the lemma in a more natural way:
Theorem surjective_pairing_stuck : (p : Z * Z),
  p = (fst p, snd p).
Proof.
  intros.
  simpl. (* Doesn't reduce anything! *)
Abort.
We have to expose the structure of p so that simpl can perform the pattern match in fst and snd. We can do this with destruct.
Theorem surjective_pairing : (p : Z * Z),
  p = (fst p, snd p).
Proof.
  intros p.
  destruct p as [n m].
  simpl.
  reflexivity.
Qed.

Exercise: 1 star, standard (snd_fst_is_swap)

Theorem snd_fst_is_swap : {A B: Type} (p : A * B),
  (snd p, fst p) = swap_pair p.
Proof.
  (* FILL IN HERE *) Admitted.

Exercise: 1 star, standard, optional (fst_swap_is_snd)

Theorem fst_swap_is_snd : {A B: Type} (p : A * B),
  fst (swap_pair p) = snd p.
Proof.
  (* FILL IN HERE *) Admitted.
End Playground.
These definitions about pairs are all built in Coq's standard library.
Print prod.
  (* Inductive prod (A B : Type) : Type :=  pair : A -> B -> A * B *)

Check fst.
  (* fst : ?A * ?B -> ?A *)

Check fst.
  (* snd : ?A * ?B -> ?B *)

Definition of Program Steps

Now, based on this definition of pairs, we can define small step semantics for program execution.
Import Abstract_Pretty_Printing.

Local Open Scope imp.

Inductive cstep : (com * state) → (com * state) → Prop :=
  | CS_AssStep : st X a a',
      astep st a a'
      cstep (CAss X a, st) (CAss X a', st)
  | CS_Ass : st1 st2 X n,
      st2 X = n
      (Y, XYst1 Y = st2 Y) →
      cstep (CAss X (ANum n), st1) (Skip, st2)
  | CS_SeqStep : st c1 c1' st' c2,
      cstep (c1, st) (c1', st') →
      cstep (c1 ;; c2 , st) (c1' ;; c2, st')
  | CS_Seq : st c2,
      cstep (Skip ;; c2, st) (c2, st)
  | CS_IfStep : st b b' c1 c2,
      bstep st b b'
      cstep
        (If b Then c1 Else c2 EndIf, st)
        (If b' Then c1 Else c2 EndIf, st)
  | CS_IfTrue : st c1 c2,
      cstep (If BTrue Then c1 Else c2 EndIf, st) (c1, st)
  | CS_IfFalse : st c1 c2,
      cstep (If BFalse Then c1 Else c2 EndIf, st) (c2, st)
  | CS_While : st b c,
      cstep
        (While b Do c EndWhile, st)
        (If b Then (c;; While b Do c EndWhile) Else Skip EndIf, st).
In this definition, we use two small tricks:
  • We use Skip as an "empty residue command".
    • An assignment command reduces to Skip (and an updated state).
    • The sequencing command waits until its left-hand subcommand has reduced to Skip, then throws it away so that reduction can continue with the right-hand subcommand.
  • We reduce a While command by transforming it into a conditional followed by the same While.
For example,
        X ::= 1;; Y ::= 2, st1
          (* in which st1(Z) = 0 for all program variables Z *)
    --> Skip;; Y ::= 2, st2
          (* in which st2(X) = 1 and
                      st2(Z) = 0 for all other program variables Z *)

    --> Y ::= 2, st2
    --> Skipst3
          (* in which st3(X) = 1 and
                      st3(Y) = 2 and
                      st3(Z) = 0 for all other program variables Z *)

Here is another example,
        While (0 ≤ XDo X ::= X - 1 EndWhilest1
          (* in which st1(Z) = 0 for all program variables Z *)
    --> If (0 ≤ X)
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst1
    --> If (0 ≤ 0)
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst1
    --> If BTrue
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst1
    --> X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhilest1
    --> X ::= 0 - 1;; While (0 ≤ XDo X ::= X - 1 EndWhilest1
    --> X ::= -1;; While (0 ≤ XDo X ::= X - 1 EndWhilest1
    --> Skip;; While (0 ≤ XDo X ::= X - 1 EndWhilest2
          (* in which st2(X) = -1 and
                      st2(Z) = 0 for all program variables Z *)

    --> While (0 ≤ XDo X ::= X - 1 EndWhilest2
    --> If (0 ≤ X)
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst2
    --> If (0 ≤ -1)
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst2
    --> If BFalse
        Then X ::= X - 1;; While (0 ≤ XDo X ::= X - 1 EndWhile
        Else Skip
        EndIfst2
    --> Skipst2
Similar to the small step semantics for expression evaluation, the multi-step relation for cstep also has its own congruence property. We prove two typical ones here.
Definition multi_cstep: com * statecom * stateProp := clos_refl_trans cstep.

Theorem multi_congr_CSeq: st1 c1 st1' c1' c2,
  multi_cstep (c1, st1) (c1', st1') →
  multi_cstep (c1 ;; c2, st1) (c1';; c2, st1').
Proof.
  intros.
  unfold multi_cstep in H.
  unfold multi_cstep.
  apply Operators_Properties.clos_rt_rtn1_iff.
  apply Operators_Properties.clos_rt_rtn1_iff in H.
  induction H.
  + (** Oops, Coq does not generate a proof goal as we expected. *)
Abort.
The problem here is, Coq's induction tactic on inductive predicates requires that predicate takes only Coq variables as its argument. Any other internal structures are not allowed, or else necessary information might be lost. In the case above, we do induction over:
    Hclos_refl_trans_n1 cstep (c1st1) (c1'st1')
In this hypothesis, (c1, st1) and (c1', st1') are not simple Coq variables. Coq provides remember tactic for necessary transitions.
Theorem multi_congr_CSeq: st1 c1 st1' c1' c2,
  multi_cstep (c1, st1) (c1', st1') →
  multi_cstep (c1 ;; c2, st1) (c1';; c2, st1').
Proof.
  intros.
  unfold multi_cstep in H.
  unfold multi_cstep.
  apply Operators_Properties.clos_rt_rtn1_iff.
  apply Operators_Properties.clos_rt_rtn1_iff in H.
  remember (c1, st1) as cst eqn:H0.
  remember (c1', st1') as cst' eqn:H1.
These two commands say: use cst and cst' to represent those two pairs. And use two equalities H0 and H1 to record this replacement.
  revert c1 st1 c1' st1' H0 H1; induction H.
  + intros.
This branch corresponds to the case that
        multi_cstep (c1st1) (c1'st1')
is true because of reflexivity.
    subst.
Remember, H1 actually says
         pair c1 st1 = pair c1' st1'
and pair is a constructor for product type. Since all constructors are injective, we can achieve c1 = c1' and st1 = st1'.
    injection H1 as ?H ?H.
    subst.
    apply rtn1_refl.
  + intros.
This branch corresponds to the case that
        multi_cstep (c1st1) (c1'st1')
is true because there exists another pair c1'', st'', such that
        multi_cstep (c1st1) (c1''st1'')
        cstep (c1''st1'') (c1'st1')
holds.
    subst.
    destruct y as [c1'' st1''].
    eapply rtn1_trans.
    - apply CS_SeqStep.
      exact H.
    - apply IHclos_refl_trans_n1.
      * reflexivity.
      * reflexivity.
Qed.
This next example is about if-then-else commands.
Theorem multi_congr_CIf: st b b' c1 c2,
  multi_bstep st b b'
  multi_cstep
    (If b Then c1 Else c2 EndIf, st)
    (If b' Then c1 Else c2 EndIf, st).
Proof.
  intros.
  unfold multi_cstep in H.
  unfold multi_cstep.
  apply Operators_Properties.clos_rt_rtn1_iff.
  apply Operators_Properties.clos_rt_rtn1_iff in H.
  induction H.
  + apply rtn1_refl.
  + eapply rtn1_trans.
    - apply CS_IfStep.
      exact H.
    - exact IHclos_refl_trans_n1.
Qed.

Administrative Steps and Alternative Definitions

In cstep's definitions, those steps defined by CS_Seq seem not natural. They actually do nothing — no evaluation is involved, no real control flow actions is involved and no new value is stored into program states. Sometimes, we will call them administrative steps.
If you find these administrative steps annoying, you may prefer to write alternative definitions as follows.
Inductive cstep_halt': comProp :=
  | CH_Skip: cstep_halt' Skip
  | CH_Seq: c1 c2,
              cstep_halt' c1
              cstep_halt' c2
              cstep_halt' (c1;; c2).
In this definition, a program halts not only at Skip but at any sequential combination of Skips.
Inductive cstep' : (com * state) → (com * state) → Prop :=
  | CS_AssStep' : st X a a',
      astep st a a'
      cstep' (CAss X a, st) (CAss X a', st)
  | CS_Ass' : st1 st2 X n,
      st2 X = n
      (Y, XYst1 Y = st2 Y) →
      cstep' (CAss X (ANum n), st1) (Skip, st2)
  | CS_SeqStep' : st c1 c1' st' c2,
      cstep' (c1, st) (c1', st') →
      cstep' (c1 ;; c2 , st) (c1' ;; c2, st')
  | CS_Seq' : st c1 c2 st' c2',
      cstep_halt' c1
      cstep' (c2, st) (c2', st') →
      cstep' (c1 ;; c2, st) (c2', st') (* <- This is different. *)
  | CS_IfStep' : st b b' c1 c2,
      bstep st b b'
      cstep'
        (If b Then c1 Else c2 EndIf, st)
        (If b' Then c1 Else c2 EndIf, st)
  | CS_IfTrue' : st c1 c2,
      cstep' (If BTrue Then c1 Else c2 EndIf, st) (c1, st)
  | CS_IfFalse' : st c1 c2,
      cstep' (If BFalse Then c1 Else c2 EndIf, st) (c2, st)
  | CS_While' : st b c,
      cstep'
        (While b Do c EndWhile, st)
        (If b Then (c;; While b Do c EndWhile) Else Skip EndIf, st).
The main idea is: if the left side of a sequential composition only has Skips, they should be removed in zero steps.
For example,
        X ::= 1;; Y ::= 2, st1
          (* in which st1(Z) = 0 for all program variables Z *)
    --> Skip;; Y ::= 2, st2
          (* in which st2(X) = 1 and
                      st2(Z) = 0 for all other program variables Z *)

    --> Skipst3
          (* in which st3(X) = 1 and
                      st3(Y) = 2 and
                      st3(Z) = 0 for all other program variables Z *)

Another alternative choice is to eliminate extra Skips after every steps. Here is the definition.
Inductive cstep'' : (com * state) → (com * state) → Prop :=
  | CS_AssStep'' : st X a a',
      astep st a a'
      cstep'' (CAss X a, st) (CAss X a', st)
  | CS_Ass'' : st1 st2 X n,
      st2 X = n
      (Y, XYst1 Y = st2 Y) →
      cstep'' (CAss X (ANum n), st1) (Skip, st2)
  | CS_SeqStep'' : st c1 c1' st' c2,
      cstep'' (c1, st) (c1', st') →
      c1'Skip(* <- This is different. *)
      cstep'' (c1 ;; c2 , st) (c1' ;; c2, st')
  | CS_Seq'' : st c1 c2 st',
      cstep'' (c1, st) (Skip, st') →
      cstep'' (c1 ;; c2 , st) (c2, st') (* <- This is different. *)
  | CS_IfStep'' : st b b' c1 c2,
      bstep st b b'
      cstep''
        (If b Then c1 Else c2 EndIf, st)
        (If b' Then c1 Else c2 EndIf, st)
  | CS_IfTrue'' : st c1 c2,
      cstep'' (If BTrue Then c1 Else c2 EndIf, st) (c1, st)
  | CS_IfFalse'' : st c1 c2,
      cstep'' (If BFalse Then c1 Else c2 EndIf, st) (c2, st)
  | CS_While'' : st b c,
      cstep''
        (While b Do c EndWhile, st)
        (If b Then (c;; While b Do c EndWhile) Else Skip EndIf, st).
For example,
        X ::= 1;; Y ::= 2, st1
          (* in which st1(Z) = 0 for all program variables Z *)
    --> Y ::= 2, st2
          (* in which st2(X) = 1 and
                      st2(Z) = 0 for all other program variables Z *)

    --> Skipst3
          (* in which st3(X) = 1 and
                      st3(Y) = 2 and
                      st3(Z) = 0 for all other program variables Z *)

This cstep'' should be used restrictly since it cannot properly handle programs that involve
  • Sequential composition with Skip,
  • Empty loop body.
(* Mon Apr 15 16:05:07 UTC 2019 *)