Lecture notes 20210329 Small Step Semantics 2

Remark. Some material in this lecture is from << Software Foundation >> volume 1 and volume 2.
Require Import Coq.micromega.Psatz.
Require Import PL.Imp.
Require Import PL.ImpExt.
Require Import PL.RTClosure.

Import Assertion_D.
Import Abstract_Pretty_Printing.
Local Open Scope imp.

Review: Small Step

We learnt to describe program behavior via steps last time. Specifically, we defined a "small-step" relation between pairs of residue programs and program states: cstep (c1, st1) (c2, st2).

Simulation for Program Equivalence

Associativity of Sequential Composition

We have defined program equivalence via denotational semantics weeks ago. We will consider a new approach based on small step semantics. Specifically, we can surely say two programs c1 and c2 have the same behavior if their execution processes are step-wise corresponding to each other.
For example, we know that c1 ;; (c2 ;; c3) and (c1 ;; c2);; c3 have the same behavior for any c1, c2 and c3, not only in the sense that they have the same pairs of initial states and ending states, but also in the sense that each one of them can simulate the other's execution step by step.
Here is a brief illustration:
      ( c1  ;; (c2 ;; c3), st1  )        ( (c1  ;; c2) ;; c3st1  )
                   |                                  |
                   |                                  |
                   v                                  v
      ( c1' ;; (c2 ;; c3), st1' )        ( (c1' ;; c2) ;; c3st1' )
                   |                                  |
                 [...]                              [...]
                   |                                  |
                   v                                  v
      ( Skip ;; (c2 ;; c3), st2 )        ( (Skip ;; c2) ;; c3st2 )
                   |                                  |
                   |                                  |
                   v                                  v
                   ------> ( c2  ;; c3st2  ) <-------
                                    |
                                    |
                                    v
                           ( c2' ;; c3st2' )
                                    |
                                   ...
                                    |
                                    v
                           ( Skip ;; c3st3 )
                                    |
                                    |
                                    v
                              ( c3  , st3  )
                                    |
                                    |
                                    v
                              ( c3' , st3' )
                                    |
                                   ...
                                    |
                                    v
                              ( Skipst4 )
We can formally define this correspondence.
Module SeqAssoc.

Inductive match_com: com -> com -> Prop :=
| MatSeqAssoc: c1 c2 c3,
    match_com (c1 ;; (c2 ;; c3)) ((c1 ;; c2);; c3)
| MatRefl: c,
    match_com c c.
And we can proof the simulation between such two programs. Here, simulation mean: whenever one side takes a step, the other side can take a corresponding step.
                              step
                 A          -------->         B

                 |                            .
          match  |                            . match
                 |                            .
                 v                            v

                 A'         - - - - >         B' ()
                              step
Lemma L_simulate_R: c1 c1' c2' st1 st2,
  match_com c1 c1' ->
  cstep (c1', st1) (c2', st2) ->
  c2,
  match_com c2 c2'cstep (c1, st1) (c2, st2).
The proof is straightforward. We can prove it by a case analysis over
         match_com c1 c2.
    
Case 1: MatRefl case, i.e. c1 = c2. The conclusion is obviously true.
Case 2: MatSeqAssoc case, i.e. c1 = c1_1 ;; (c1_2 ;; c1_3)
Case 2-1: cstep (c1_1, st1) (c2_1, st2). New simulation is still based on MatSeqAssoc.
Case 2-1: c1_1 = CSkip. New simulation is based MatRefl.
Proof.
  intros.
  inversion H as [c1_1 c1_2 c1_3 |]; subst.
  2: { c2'; split; [constructor | exact H0]. }
  inversion H0; subst.
  inversion H2 as [| | ? ? c2_1 | | | | |]; subst; clear H0 H2.
  + (c2_1 ;; (c1_2 ;; c1_3)).
    split.
    - apply MatSeqAssoc.
    - constructor.
      apply H1.
  + rename c1' into c1_2.
    (c1_2 ;; c1_3).
    split.
    - apply MatRefl.
    - constructor.
Qed.
The other direction is simular.
Lemma R_simulate_L: c1 c2 c1' st st',
  match_com c1 c2 ->
  cstep (c1, st) (c1', st') ->
  c2',
  match_com c1' c2'cstep (c2, st) (c2', st').
Proof.
  intros.
  inversion H; subst.
  2: { c1'; split; [constructor | exact H0]. }
  inversion H0; subst.
  + ((c1'0 ;; c3) ;; c4).
    split.
    - apply MatSeqAssoc.
    - constructor.
      constructor.
      apply H2.
  + (c3 ;; c4).
    split.
    - apply MatRefl.
    - constructor.
      constructor.
Qed.

End SeqAssoc.

Removing Skips On the Left

We know that Skip does nothing and can be removed. We proved that c, c;; Skip and Skip;; c behave the same based on denotational semantics. Today, we first define a function to remove all Skip on the left side of sequential composition.
Fixpoint remove_skip (c: com): com :=
  match c with
  | CSkip
      CSkip
  | CAss X E
      CAss X E
  | CSeq c1 c2
      match remove_skip c1 with
      | CSkipremove_skip c2
      | _CSeq (remove_skip c1) (remove_skip c2)
      end
  | CIf b c1 c2
      CIf b (remove_skip c1) (remove_skip c2)
  | CWhile b c1
      CWhile b (remove_skip c1)
  end.

Example remove_skip_example_1:
  remove_skip (Skip ;; (Skip ;; While BTrue Do Skip EndWhile)) =
  While BTrue Do Skip EndWhile.
Proof. reflexivity. Qed.

Example remove_skip_example_2:
  remove_skip ((Skip ;; Skip) ;; While BTrue Do Skip EndWhile) =
  While BTrue Do Skip EndWhile.
Proof. reflexivity. Qed.

Example remove_skip_example_3: (X: var),
  remove_skip (Skip ;; X ::= 0 ;; Skip) =
  (X ::= 0 ;; Skip).
Proof. intros. reflexivity. Qed.
It is easy to prove that c and remove_skip c always have the same denotation. But we can prove stronger claims based on small step semantics. That is, the steps of executing c will be simulated by the steps of executing remove_skip c, though some redundant skip-step can be removed. This conclusion can be formally stated as follows.
Definition cstep_or_not (X Y: com * state): Prop :=
  X = Ycstep X Y.

Definition SimulationStatement_01 (match_com: com -> com -> Prop): Prop :=
  c1 c2 c1' st st',
  match_com c1 c2 ->
  cstep (c1, st) (c1', st') ->
  c2',
    match_com c1' c2'cstep_or_not (c2, st) (c2', st').
Let's start with an naive definition of match_com.
Module RemoveSkip_First_Attempt.

Definition match_com (c1 c2: com): Prop := c2 = remove_skip c1.
The following statement says: when c takes one step, remove_skip c will take zero step or one corresponding step.
Lemma R_simulate_L: c1 c2 c1' st st',
  match_com c1 c2 ->
  cstep (c1, st) (c1', st') ->
  c2',
    match_com c1' c2'
    ((c2, st) = (c2', st') ∨ cstep (c2, st) (c2', st')).
Unfortunately, this statement is not true! Consider the execution of
       X ::= 1;; Y ::= 0.
    
According to small step semantics, it takes three steps.
       X ::= 1;; Y ::= 0 -->
       Skip;; Y ::= 0 -->
       Y ::= 0 -->
       Skip
But remove_skip (Skip;; Y ::= 0) = (Y ::= 0). Thus, the first step above does not have a correspondence when the remove_skip transformation is taken. You can find where a Coq proof attempt will fail in the following scripts.
Proof.
  intros.
  unfold match_com in *.
  subst c2.
  (remove_skip c1').
  split; [reflexivity |].
  induction_cstep H0; simpl; intros.
  + simpl.
    right.
    constructor; tauto.
  + simpl.
    right.
    constructor; tauto.
  +
Abort.

End RemoveSkip_First_Attempt.

Module RemoveSkip.
In order to establish a simulation relation for remove_skip, we define a weaker version of match_com. The main idea is to reserve some left-side skips.
Fixpoint match_com (c1 c2: com): Prop :=
  match c1 with
  | c1_1 ;; c1_2
      remove_skip c1_1 = CSkipc2 = remove_skip c1_2
      c2_1, match_com c1_1 c2_1c2 = (c2_1 ;; remove_skip c1_2)
  | CIf b c1_1 c1_2
      c2_1, match_com c1_1 c2_1c2 = CIf b c2_1 (remove_skip c1_2)
  | _
      c2 = remove_skip c1
  end.
For example, consider X ::= 1;; (Skip;; Y ::= 2), which will be transfered to X ::= 1;; Y ::=2 by remove_skip.
      X ::= 1;; (Skip;; Y ::= 2)               X ::= 1;; Y ::= 2
                   |                                  |
                   |                                  |
                   v                                  v
        Skip;; (Skip;; Y ::= 2)                  Skip;; Y ::= 2
                   |                                 | |
                   |                                 | |  (no step)
                   v                                 | |
            Skip;; Y ::= 2                       Skip;; Y ::= 2
                   |                                  |
                   |                                  |
                   v                                  v
                Y ::= 2                            Y ::= 2
                   |                                  |
                   |                                  |
                   v                                  v
                  Skip                               Skip
This new definition of match_com allows Skip;; (Skip;; Y ::= 2) to match Skip;; Y ::= 2, but not only restricted to Y ::= 2.
Here we prove this match_com relation is indeed a weaker one comparing to remove_skip.
Lemma match_com_remove_skip: c, match_com c (remove_skip c).
Proof.
  intros.
  induction c; simpl; try tauto.
  + destruct (remove_skip c1); try eauto.
  + eauto.
Qed.
Our proof strategy for simulation (defined as follows)
         c1 c2 c1' st st',
           match_com c1 c2 ->
           cstep (c1st) (c1'st') ->
           c2',
             match_com c1' c2' ∧
             cstep_or_not (c2st) (c2'st')
is based on an induction over its assumption
         cstep (c1st) (c1'st').
    
Corresponding to cstep's definition, our inductive proof includes 8 cases. For example, the proof step for the CS_AssStep case is to prove:
         astep st a a' ->
         match_com (CAss X ac2 ->
         c2',
           match_com (CAss X a'c2' ∧
           cstep_or_not (c2st) (c2'st)
which will be proved later (Lemma R_simulate_L_CS_AssStep).
Among all 8 cases, the most intersting case is the CS_SeqStep case, and we need two auxiliary lemmas for establishing the proof.
The following lemma remove_skip_skip_step says, if c1 can be transformed to Skip by remove_skip (i.e. it is a sequential composition of many skips), then taking one step from c1 results in another command c2 s.t. remove_skip c2 = Skip as well. The main proof idea is to apply an induction over c1's structure.
Lemma remove_skip_skip_step: c1 c2 st1 st2,
  remove_skip c1 = CSkip ->
  cstep (c1, st1) (c2, st2) ->
  st1 = st2remove_skip c2 = CSkip.
Proof.
  intros.
  revert st1 c2 st2 H0.
  induction c1; try solve [inversion H]; intros.
  + inversion H0.
  + simpl in H.
    destruct (remove_skip c1_1) eqn:?H; try solve [inversion H].
    specialize (IHc1_1 eq_refl).
    inversion H0; subst.
    - specialize (IHc1_1 _ _ _ H3).
      destruct IHc1_1.
      split; [auto | simpl].
      rewrite H4, H.
      reflexivity.
    - auto.
Qed.
The following lemma says, if one or zero step takes (c1, st) to (c1', st') , then one or zero step can also take (c1;; c2, st) to (c1';;c2, st') . Its proof is straightforward.
Lemma cstep_or_not_congr_CSeq: c1 st c1' st' c2,
  cstep_or_not (c1, st) (c1', st') ->
  cstep_or_not (c1;; c2, st) (c1';; c2, st').
Proof.
  intros.
  destruct H; [left | right].
  + injection H as ? ?; subst; reflexivity.
  + apply CS_SeqStep, H.
Qed.
The following lemma states the induction step of CS_SeqStep. Since
         match_com (c1_1;; c1_2c2,
either c2 has a form of c2_1 ;; c2_2 s.t.
  • match_com c1_1 c2_1;
  • remove_skip c1_2 = c2_2;
or c2 = remove_skip c1_2 and remove_skip c1_1 = CSkip. We prove the following lemma by such a case analysis.
Lemma R_simulate_L_CS_SeqStep: c1_1 c1_2 st c1_1' st' c2
  (IH:
     c2_1,
       match_com c1_1 c2_1 ->
       c2_1',
         match_com c1_1' c2_1'
         cstep_or_not (c2_1, st) (c2_1', st')),
  cstep (c1_1, st) (c1_1', st') ->
  match_com (c1_1;; c1_2) c2 ->
  c2',
    match_com (c1_1';; c1_2) c2'
    cstep_or_not (c2, st) (c2', st').
Proof.
  intros.
  simpl.
  simpl in H0.
  destruct H0 as [[? ?] | [c2_1 [? ?]]].
  + pose proof remove_skip_skip_step _ _ _ _ H0 H as [? ?].
    subst st'.
    c2.
    split; [| left; reflexivity].
    subst c2; simpl; rewrite H3.
    auto.
  + specialize (IH _ H0) as [c2_1' [? ?]].
    (c2_1' ;; remove_skip c1_2).
    split; [right; eauto |].
    subst c2.
    apply cstep_or_not_congr_CSeq, H3.
Qed.
The following lemma proves the CS_Seq case. Its assumption
         match_com (CSkip;; c1_2c2
also means two possibilities:
  • c2 = CSkip;; remove_skip c1_2;
  • c2 = remove_skip c1_2.
Lemma R_simulate_L_CS_Seq: c1_2 c2 st,
  match_com (CSkip;; c1_2) c2 ->
  c2',
    match_com c1_2 c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  destruct H as [[? ?] | [c2_1 [? ?]]].
  + clear H.
    subst c2.
    (remove_skip c1_2).
    split; [apply match_com_remove_skip | left; reflexivity].
  + inversion H; subst.
    clear H.
    simpl (remove_skip Skip).
    (remove_skip c1_2).
    split; [apply match_com_remove_skip |].
    right; apply CS_Seq.
Qed.
We then list other proof steps for building R_simulate_L.
Lemma R_simulate_L_CS_AssStep: X a a' c2 st,
  astep st a a' ->
  match_com (CAss X a) c2 ->
  c2',
    match_com (CAss X a') c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  inversion H0; subst; clear H0.
  (X ::= a').
  split; [constructor |].
  right; constructor; tauto.
Qed.

Lemma R_simulate_L_CS_Ass: X n c2 st1 st2,
  st2 X = n ->
  (Y : var, XY -> st1 Y = st2 Y) ->
  match_com (CAss X (ANum n)) c2 ->
  c2',
    match_com CSkip c2'
    cstep_or_not (c2, st1) (c2', st2).
Proof.
  intros.
  inversion H1; subst; clear H1.
  CSkip.
  split; [constructor |].
  right; apply CS_Ass; tauto.
Qed.

Lemma R_simulate_L_CS_IfStep: b b' c1_1 c1_2 c2 st,
  bstep st b b' ->
  match_com (If b Then c1_1 Else c1_2 EndIf) c2 ->
  c2',
    match_com (If b' Then c1_1 Else c1_2 EndIf) c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  simpl.
  simpl in H0.
  destruct H0 as [c2_1 [? ?]].
  subst c2.
  (If b' Then c2_1 Else (remove_skip c1_2) EndIf).
  split; [eauto |].
  right; apply CS_IfStep, H.
Qed.

Lemma R_simulate_L_CS_IfTrue: c1_1 c1_2 c2 st,
  match_com (If BTrue Then c1_1 Else c1_2 EndIf) c2 ->
  c2',
    match_com c1_1 c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  simpl.
  simpl in H.
  destruct H as [c2_1 [? ?]].
  subst c2.
  c2_1.
  split; [exact H |].
  right; apply CS_IfTrue.
Qed.

Lemma R_simulate_L_CS_IfFalse: c1_1 c1_2 c2 st,
  match_com (If BFalse Then c1_1 Else c1_2 EndIf) c2 ->
  c2',
    match_com c1_2 c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  simpl.
  simpl in H.
  destruct H as [c2_1 [? ?]].
  subst c2.
  (remove_skip c1_2).
  split; [apply match_com_remove_skip |].
  right; apply CS_IfFalse.
Qed.

Lemma R_simulate_L_CS_While: b c1_1 c2 st,
  match_com (While b Do c1_1 EndWhile) c2 ->
  c2',
    match_com (If b Then c1_1;; While b Do c1_1 EndWhile Else Skip EndIf) c2'
    cstep_or_not (c2, st) (c2', st).
Proof.
  intros.
  simpl in H.
  subst c2.
  (CIf b (remove_skip c1_1;; CWhile b (remove_skip c1_1)) Skip).
  split.
  + (remove_skip c1_1;; CWhile b (remove_skip c1_1)).
    split.
    - right.
      (remove_skip c1_1).
      split; [apply match_com_remove_skip | reflexivity].
    - reflexivity.
  + right.
    apply CS_While.
Qed.
Adding these proof steps together, we can prove the simulation.
Theorem R_simulate_L: SimulationStatement_01 match_com.
       c1 c2 c1' st st',
         match_com c1 c2 ->
         cstep (c1st) (c1'st') ->
         c2',
           match_com c1' c2' ∧ cstep_or_not (c2st) (c2'st').
    
Proof.
  unfold SimulationStatement_01.
  intros.
  revert c2 H; induction_cstep H0; intros.
  + eapply R_simulate_L_CS_AssStep; eassumption.
  + eapply R_simulate_L_CS_Ass; eassumption.
  + eapply R_simulate_L_CS_SeqStep; eassumption.
  + eapply R_simulate_L_CS_Seq; eassumption.
  + eapply R_simulate_L_CS_IfStep; eassumption.
  + eapply R_simulate_L_CS_IfTrue; eassumption.
  + eapply R_simulate_L_CS_IfFalse; eassumption.
  + eapply R_simulate_L_CS_While; eassumption.
Qed.

End RemoveSkip.

Properties of Simulation Relations

Simulation relations describes correspondences between single steps. Naturally, we would like to extend such discussion to multi-step relations.
Definition SimulationStatement_1 (match_com: com -> com -> Prop): Prop :=
  c1 c2 c1' st st',
  match_com c1 c2 ->
  cstep (c1, st) (c1', st') ->
  c2',
    match_com c1' c2'cstep (c2, st) (c2', st').

Lemma multi_cstep_Simulation_1: match_com,
    SimulationStatement_1 match_com ->
    c1 c2 c1' st st',
      match_com c1 c2 ->
      multi_cstep (c1, st) (c1', st') ->
      c2',
        match_com c1' c2'multi_cstep (c2, st) (c2', st').
Proof.
  intros.
  revert c2 H0; induction_1n H1; intros.
  + c2.
    split; [exact H0 | reflexivity].
  + pose proof H _ _ _ _ _ H2 H0.
    destruct H3 as [c' [? ?]].
    specialize (IHrt c' H3).
    destruct IHrt as [c2' [? ?]].
    c2'.
    split; [exact H5 |].
    etransitivity_1n; [exact H4 | exact H6].
Qed.
The following lemma says, such properties about multi-step relation also holds for SimulationStatement_01.
Lemma multi_cstep_Simulation_01: match_com,
    SimulationStatement_01 match_com ->
    c1 c2 c1' st st',
      match_com c1 c2 ->
      multi_cstep (c1, st) (c1', st') ->
      c2',
        match_com c1' c2'multi_cstep (c2, st) (c2', st').
Proof.
  intros.
  revert c2 H0; induction_1n H1; intros.
  + c2.
    split; [exact H0 | reflexivity].
  + pose proof H _ _ _ _ _ H2 H0.
    destruct H3 as [c' [? ?]].
    specialize (IHrt c' H3).
    destruct IHrt as [c2' [? ?]].
    c2'.
    split; [exact H5 |].
    destruct H4.
    - injection H4 as ? ?; subst.
      exact H6.
    - etransitivity_1n; [exact H4 | exact H6].
Qed.

More Reading: Unused Assignments

Here we discuss another issue, unused assignments. For example,
       X ::= 1;; Y ::= 2;; X ::= 3
can be optimized to
       Y ::= 2;; X ::= 3
since the first assignment X ::= 1 is an unused assignment. Every step in the former program's execution will be simulated by the latter one, execept those steps for the redundant assignment. However, we cannot prove a similar simulation relation for it like remove_skip.
        X ::= 1;; Y ::= 2;; X ::= 3     (State Match)      Y ::= 2;; X ::= 3
                   |                                             | |
                   |                                             | | (no step)
                   v                                             | |
         Skip;; Y ::= 2;; X ::= 3      (State Mismatch)    Y ::= 2;; X ::= 3
                   |                                             | |
                   |                                             | | (no step)
                   v                                             | |
            Y ::= 2;; X ::= 3          (State Mismatch)    Y ::= 2;; X ::= 3
                   |                                              |
                   |                                              |
                   v                                              v
             Skip;; X ::= 3            (State Mismatch)      Skip;; X ::= 3
                   |                                              |
                   |                                              |
                   v                                              v
                X ::= 3                (State Mismatch)         X ::= 3
                   |                                              |
                   |                                              |
                   v                                              v
                 Skip                   (State Match)           Skip
Thus, we need a different simulation statement.
Definition SimulatationStatement_01'
           (match_com_state: com * state -> com * state -> Prop) :=
  cst1 cst2 cst1',
    match_com_state cst1 cst2 ->
    cstep cst1 cst1' ->
    cst2',
      match_com_state cst1' cst2'
      cstep_or_not cst2 cst2'.

(* 2021-03-29 18:49 *)