(** * Equiv: Program Equivalence *) Set Warnings "-notation-overridden,-parsing". From PLF Require Import Maps. From Coq Require Import Bool.Bool. From Coq Require Import Arith.Arith. From Coq Require Import Init.Nat. From Coq Require Import Arith.PeanoNat. Import Nat. From Coq Require Import Arith.EqNat. From Coq Require Import Lia. From Coq Require Import Lists.List. From Coq Require Import Logic.FunctionalExtensionality. Import ListNotations. From PLF Require Export Imp. (* ################################################################# *) (** * Behavioral Equivalence *) (** In the last chapter, we studied a simple transformation on arithmetic expressions and showed that it was correct in the sense that it preserved the value of the expressions. To play a similar game with programs involving mutable state, we need a more sophisticated notion of correctness, called _behavioral equivalence_. *) (* ================================================================= *) (** ** Definitions *) (** For [aexp]s and [bexp]s with variables, the definition we want is clear: Two [aexp]s or [bexp]s are _behaviorally equivalent_ if they evaluate to the same result in every state. *) Definition aequiv (a1 a2 : aexp) : Prop := forall (st : state), aeval st a1 = aeval st a2. Definition bequiv (b1 b2 : bexp) : Prop := forall (st : state), beval st b1 = beval st b2. Theorem aequiv_example: aequiv <{ X - X }> <{ 0 }>. Proof. intros st. simpl. lia. Qed. Theorem bequiv_example: bequiv <{ X - X = 0 }> <{ true }>. Proof. intros st. unfold beval. rewrite aequiv_example. reflexivity. Qed. (** For commands, we need to deal with the possibility of nontermination. We do this by defining command equivalence as "if the first one terminates in a particular state then so does the second, and vice versa": *) Definition cequiv (c1 c2 : com) : Prop := forall (st st' : state), (st =[ c1 ]=> st') <-> (st =[ c2 ]=> st'). (* QUIZ Are these two programs equivalent? X := 1; Y := 2 and Y := 2; X := 1 (1) Yes (2) No (3) Not sure *) (* QUIZ What about these? X := 1; Y := 2 and X := 2; Y := 1 (1) Yes (2) No (3) Not sure *) (* QUIZ What about these? while 1 <= X do X := X + 1 end and while 2 <= X do X := X + 1 end (1) Yes (2) No (3) Not sure *) (* QUIZ These? while true do while false do X := X + 1 end end and while false do while true do X := X + 1 end end (1) Yes (2) No (3) Not sure *) (* ================================================================= *) (** ** Simple Examples *) (** For examples of command equivalence, let's start by looking at some trivial program transformations involving [skip]: *) Theorem skip_left : forall c, cequiv <{ skip; c }> c. Proof. (* WORK IN CLASS *) Admitted. (** Similarly, here is a simple transformation that optimizes [if] commands: *) Theorem if_true_simple : forall c1 c2, cequiv <{ if true then c1 else c2 end }> c1. Proof. intros c1 c2. split; intros H. - (* -> *) inversion H; subst. assumption. discriminate. - (* <- *) apply E_IfTrue. reflexivity. assumption. Qed. (** Of course, no (human) programmer would write a conditional whose guard is literally [true]. But they might write one whose guard is _equivalent_ to true: *) Theorem if_true: forall b c1 c2, bequiv b <{true}> -> cequiv <{ if b then c1 else c2 end }> c1. Proof. intros b c1 c2 Hb. split; intros H. - (* -> *) inversion H; subst. + (* b evaluates to true *) assumption. + (* b evaluates to false (contradiction) *) unfold bequiv in Hb. simpl in Hb. rewrite Hb in H5. discriminate. - (* <- *) apply E_IfTrue; try assumption. unfold bequiv in Hb. simpl in Hb. apply Hb. Qed. (** Similarly: *) Theorem if_false : forall b c1 c2, bequiv b <{false}> -> cequiv <{ if b then c1 else c2 end }> c2. Proof. (* FILL IN HERE *) Admitted. (** For [while] loops, we can give a similar pair of theorems. A loop whose guard is equivalent to [false] is equivalent to [skip], while a loop whose guard is equivalent to [true] is equivalent to [while true do skip end] (or any other non-terminating program). The first of these facts is easy. *) Theorem while_false : forall b c, bequiv b <{false}> -> cequiv <{ while b do c end }> <{ skip }>. Proof. intros b c Hb. split; intros H. - (* -> *) inversion H; subst. + (* E_WhileFalse *) apply E_Skip. + (* E_WhileTrue *) rewrite Hb in H2. discriminate. - (* <- *) inversion H; subst. apply E_WhileFalse. apply Hb. Qed. (** To prove the second fact, we need an auxiliary lemma stating that [while] loops whose guards are equivalent to [true] never terminate. *) Lemma while_true_nonterm : forall b c st st', bequiv b <{true}> -> ~( st =[ while b do c end ]=> st' ). Proof. (* WORK IN CLASS *) Admitted. Theorem while_true : forall b c, bequiv b <{true}> -> cequiv <{ while b do c end }> <{ while true do skip end }>. Proof. (* FILL IN HERE *) Admitted. (** A more interesting fact about [while] commands is that any number of copies of the body can be "unrolled" without changing meaning. Loop unrolling is a common transformation in real compilers. *) Theorem loop_unrolling : forall b c, cequiv <{ while b do c end }> <{ if b then c ; while b do c end else skip end }>. Proof. (* WORK IN CLASS *) Admitted. (* ################################################################# *) (** * Properties of Behavioral Equivalence *) (** We next consider some fundamental properties of program equivalence. *) (* ================================================================= *) (** ** Behavioral Equivalence Is an Equivalence *) (** First, we verify that the equivalences on [aexps], [bexps], and [com]s really are _equivalences_ -- i.e., that they are reflexive, symmetric, and transitive. The proofs are all easy. *) Lemma refl_aequiv : forall (a : aexp), aequiv a a. Proof. intros a st. reflexivity. Qed. Lemma sym_aequiv : forall (a1 a2 : aexp), aequiv a1 a2 -> aequiv a2 a1. Proof. intros a1 a2 H. intros st. symmetry. apply H. Qed. Lemma trans_aequiv : forall (a1 a2 a3 : aexp), aequiv a1 a2 -> aequiv a2 a3 -> aequiv a1 a3. Proof. unfold aequiv. intros a1 a2 a3 H12 H23 st. rewrite (H12 st). rewrite (H23 st). reflexivity. Qed. Lemma refl_bequiv : forall (b : bexp), bequiv b b. Proof. unfold bequiv. intros b st. reflexivity. Qed. Lemma sym_bequiv : forall (b1 b2 : bexp), bequiv b1 b2 -> bequiv b2 b1. Proof. unfold bequiv. intros b1 b2 H. intros st. symmetry. apply H. Qed. Lemma trans_bequiv : forall (b1 b2 b3 : bexp), bequiv b1 b2 -> bequiv b2 b3 -> bequiv b1 b3. Proof. unfold bequiv. intros b1 b2 b3 H12 H23 st. rewrite (H12 st). rewrite (H23 st). reflexivity. Qed. Lemma refl_cequiv : forall (c : com), cequiv c c. Proof. unfold cequiv. intros c st st'. reflexivity. Qed. Lemma sym_cequiv : forall (c1 c2 : com), cequiv c1 c2 -> cequiv c2 c1. Proof. unfold cequiv. intros c1 c2 H st st'. rewrite H. reflexivity. Qed. Lemma trans_cequiv : forall (c1 c2 c3 : com), cequiv c1 c2 -> cequiv c2 c3 -> cequiv c1 c3. Proof. unfold cequiv. intros c1 c2 c3 H12 H23 st st'. rewrite H12. apply H23. Qed. (* ================================================================= *) (** ** Behavioral Equivalence Is a Congruence *) (** Less obviously, behavioral equivalence is also a _congruence_. That is, the equivalence of two subprograms implies the equivalence of the larger programs in which they are embedded: aequiv a a' ------------------------- cequiv (x := a) (x := a') cequiv c1 c1' cequiv c2 c2' -------------------------- cequiv (c1;c2) (c1';c2') ... and so on for the other forms of commands. *) (** These properties allow us to reason that a large program behaves the same when we replace a small part with something equivalent. *) Theorem CAss_congruence : forall x a a', aequiv a a' -> cequiv <{x := a}> <{x := a'}>. Proof. intros x a a' Heqv st st'. split; intros Hceval. - (* -> *) inversion Hceval. subst. apply E_Ass. rewrite Heqv. reflexivity. - (* <- *) inversion Hceval. subst. apply E_Ass. rewrite Heqv. reflexivity. Qed. Theorem CWhile_congruence : forall b b' c c', bequiv b b' -> cequiv c c' -> cequiv <{ while b do c end }> <{ while b' do c' end }>. Proof. (* WORK IN CLASS *) Admitted. Theorem CSeq_congruence : forall c1 c1' c2 c2', cequiv c1 c1' -> cequiv c2 c2' -> cequiv <{ c1;c2 }> <{ c1';c2' }>. Proof. (* FILL IN HERE *) Admitted. Theorem CIf_congruence : forall b b' c1 c1' c2 c2', bequiv b b' -> cequiv c1 c1' -> cequiv c2 c2' -> cequiv <{ if b then c1 else c2 end }> <{ if b' then c1' else c2' end }>. Proof. (* FILL IN HERE *) Admitted. (** For example, here are two equivalent programs and a proof of their equivalence... *) Example congruence_example: cequiv (* Program 1: *) <{ X := 0; if (X = 0) then Y := 0 else Y := 42 end }> (* Program 2: *) <{ X := 0; if (X = 0) then Y := X - X (* <--- Changed here *) else Y := 42 end }>. Proof. apply CSeq_congruence. - apply refl_cequiv. - apply CIf_congruence. + apply refl_bequiv. + apply CAss_congruence. unfold aequiv. simpl. symmetry. apply minus_diag. + apply refl_cequiv. Qed. (* ################################################################# *) (** * Program Transformations *) (** A _program transformation_ is a function that takes a program as input and produces some variant of the program as output. Compiler optimizations such as constant folding are a canonical example, but there are many others. *) (** A program transformation is _sound_ if it preserves the behavior of the original program. *) Definition atrans_sound (atrans : aexp -> aexp) : Prop := forall (a : aexp), aequiv a (atrans a). Definition btrans_sound (btrans : bexp -> bexp) : Prop := forall (b : bexp), bequiv b (btrans b). Definition ctrans_sound (ctrans : com -> com) : Prop := forall (c : com), cequiv c (ctrans c). (* ================================================================= *) (** ** The Constant-Folding Transformation *) (** An expression is _constant_ when it contains no variable references. Constant folding is an optimization that finds constant expressions and replaces them by their values. *) Fixpoint fold_constants_aexp (a : aexp) : aexp := match a with | ANum n => ANum n | AId x => AId x | <{ a1 + a2 }> => match (fold_constants_aexp a1, fold_constants_aexp a2) with | (ANum n1, ANum n2) => ANum (n1 + n2) | (a1', a2') => <{ a1' + a2' }> end | <{ a1 - a2 }> => match (fold_constants_aexp a1, fold_constants_aexp a2) with | (ANum n1, ANum n2) => ANum (n1 - n2) | (a1', a2') => <{ a1' - a2' }> end | <{ a1 * a2 }> => match (fold_constants_aexp a1, fold_constants_aexp a2) with | (ANum n1, ANum n2) => ANum (n1 * n2) | (a1', a2') => <{ a1' * a2' }> end end. Example fold_aexp_ex1 : fold_constants_aexp <{ (1 + 2) * X }> = <{ 3 * X }>. Proof. reflexivity. Qed. (** Note that this version of constant folding doesn't eliminate trivial additions, etc. -- we are focusing attention on a single optimization for the sake of simplicity. It is not hard to incorporate other ways of simplifying expressions; the definitions and proofs just get longer. *) Example fold_aexp_ex2 : fold_constants_aexp <{ X - ((0 * 6) + Y) }> = <{ X - (0 + Y) }>. Proof. reflexivity. Qed. (** Not only can we lift [fold_constants_aexp] to [bexp]s (in the [BEq] and [BLe] cases); we can also look for constant _boolean_ expressions and evaluate them in-place. *) Fixpoint fold_constants_bexp (b : bexp) : bexp := match b with | <{true}> => <{true}> | <{false}> => <{false}> | <{ a1 = a2 }> => match (fold_constants_aexp a1, fold_constants_aexp a2) with | (ANum n1, ANum n2) => if n1 =? n2 then <{true}> else <{false}> | (a1', a2') => <{ a1' = a2' }> end | <{ a1 <= a2 }> => match (fold_constants_aexp a1, fold_constants_aexp a2) with | (ANum n1, ANum n2) => if n1 <=? n2 then <{true}> else <{false}> | (a1', a2') => <{ a1' <= a2' }> end | <{ ~ b1 }> => match (fold_constants_bexp b1) with | <{true}> => <{false}> | <{false}> => <{true}> | b1' => <{ ~ b1' }> end | <{ b1 && b2 }> => match (fold_constants_bexp b1, fold_constants_bexp b2) with | (<{true}>, <{true}>) => <{true}> | (<{true}>, <{false}>) => <{false}> | (<{false}>, <{true}>) => <{false}> | (<{false}>, <{false}>) => <{false}> | (b1', b2') => <{ b1' && b2' }> end end. Example fold_bexp_ex1 : fold_constants_bexp <{ true && ~(false && true) }> = <{ true }>. Proof. reflexivity. Qed. Example fold_bexp_ex2 : fold_constants_bexp <{ (X = Y) && (0 = (2 - (1 + 1))) }> = <{ (X = Y) && true }>. Proof. reflexivity. Qed. (** To fold constants in a command, we apply the appropriate folding functions on all embedded expressions. *) Fixpoint fold_constants_com (c : com) : com := match c with | <{ skip }> => <{ skip }> | <{ x := a }> => <{ x := (fold_constants_aexp a) }> | <{ c1 ; c2 }> => <{ fold_constants_com c1 ; fold_constants_com c2 }> | <{ if b then c1 else c2 end }> => match fold_constants_bexp b with | <{true}> => fold_constants_com c1 | <{false}> => fold_constants_com c2 | b' => <{ if b' then fold_constants_com c1 else fold_constants_com c2 end}> end | <{ while b do c1 end }> => match fold_constants_bexp b with | <{true}> => <{ while true do skip end }> | <{false}> => <{ skip }> | b' => <{ while b' do (fold_constants_com c1) end }> end end. Example fold_com_ex1 : fold_constants_com (* Original program: *) <{ X := 4 + 5; Y := X - 3; if ((X - Y) = (2 + 4)) then skip else Y := 0 end; if (0 <= (4 - (2 + 1))) then Y := 0 else skip end; while (Y = 0) do X := X + 1 end }> = (* After constant folding: *) <{ X := 9; Y := X - 3; if ((X - Y) = 6) then skip else Y := 0 end; Y := 0; while (Y = 0) do X := X + 1 end }>. Proof. reflexivity. Qed. (* ================================================================= *) (** ** Soundness of Constant Folding *) (** Now we need to show that what we've done is correct. *) Theorem fold_constants_aexp_sound : atrans_sound fold_constants_aexp. Proof. (* WORK IN CLASS *) Admitted. Theorem fold_constants_bexp_sound: btrans_sound fold_constants_bexp. Proof. unfold btrans_sound. intros b. unfold bequiv. intros st. induction b; (* true and false are immediate *) try reflexivity. - (* BEq *) simpl. remember (fold_constants_aexp a1) as a1' eqn:Heqa1'. remember (fold_constants_aexp a2) as a2' eqn:Heqa2'. replace (aeval st a1) with (aeval st a1') by (subst a1'; rewrite <- fold_constants_aexp_sound; reflexivity). replace (aeval st a2) with (aeval st a2') by (subst a2'; rewrite <- fold_constants_aexp_sound; reflexivity). destruct a1'; destruct a2'; try reflexivity. (* The only interesting case is when both a1 and a2 become constants after folding *) simpl. destruct (n =? n0); reflexivity. - (* BLe *) (* FILL IN HERE *) admit. - (* BNot *) simpl. remember (fold_constants_bexp b) as b' eqn:Heqb'. rewrite IHb. destruct b'; reflexivity. - (* BAnd *) simpl. remember (fold_constants_bexp b1) as b1' eqn:Heqb1'. remember (fold_constants_bexp b2) as b2' eqn:Heqb2'. rewrite IHb1. rewrite IHb2. destruct b1'; destruct b2'; reflexivity. (* FILL IN HERE *) Admitted. Theorem fold_constants_com_sound : ctrans_sound fold_constants_com. Proof. unfold ctrans_sound. intros c. induction c; simpl. - (* skip *) apply refl_cequiv. - (* := *) apply CAss_congruence. apply fold_constants_aexp_sound. - (* ; *) apply CSeq_congruence; assumption. - (* if *) assert (bequiv b (fold_constants_bexp b)). { apply fold_constants_bexp_sound. } destruct (fold_constants_bexp b) eqn:Heqb; try (apply CIf_congruence; assumption). (* (If the optimization doesn't eliminate the if, then the result is easy to prove from the IH and [fold_constants_bexp_sound].) *) + (* b always true *) apply trans_cequiv with c1; try assumption. apply if_true; assumption. + (* b always false *) apply trans_cequiv with c2; try assumption. apply if_false; assumption. - (* while *) (* FILL IN HERE *) Admitted. (* ################################################################# *) (** * Proving Inequivalence *) (** Suppose that [c1] is a command of the form [X := a1; Y := a2] and [c2] is the command [X := a1; Y := a2'], where [a2'] is formed by substituting [a1] for all occurrences of [X] in [a2]. For example, [c1] and [c2] might be: c1 = (X := 42 + 53; Y := Y + X) c2 = (X := 42 + 53; Y := Y + (42 + 53)) Clearly, this _particular_ [c1] and [c2] are equivalent. Is this true in general? *) (** More formally, here is the function that substitutes an arithmetic expression [u] for each occurrence of a given variable [x] in another expression [a]: *) Fixpoint subst_aexp (x : string) (u : aexp) (a : aexp) : aexp := match a with | ANum n => ANum n | AId x' => if eqb_string x x' then u else AId x' | <{ a1 + a2 }> => <{ (subst_aexp x u a1) + (subst_aexp x u a2) }> | <{ a1 - a2 }> => <{ (subst_aexp x u a1) - (subst_aexp x u a2) }> | <{ a1 * a2 }> => <{ (subst_aexp x u a1) * (subst_aexp x u a2) }> end. (** And here is the property we are interested in, expressing the claim that commands [c1] and [c2] as described above are always equivalent. *) Definition subst_equiv_property := forall x1 x2 a1 a2, cequiv <{ x1 := a1; x2 := a2 }> <{ x1 := a1; x2 := subst_aexp x1 a1 a2 }>. (** Sadly, the property does _not_ always hold. We can show the following counterexample: X := X + 1; Y := X If we perform the substitution, we get X := X + 1; Y := X + 1 which clearly isn't equivalent to the original program. [] *) Theorem subst_inequiv : ~ subst_equiv_property. Proof. unfold subst_equiv_property. intros Contra. (* Here is the counterexample: assuming that [subst_equiv_property] holds allows us to prove that these two programs are equivalent... *) remember <{ X := X + 1; Y := X }> as c1. remember <{ X := X + 1; Y := X + 1 }> as c2. assert (cequiv c1 c2) by (subst; apply Contra). clear Contra. (* ... allows us to show that the command [c2] can terminate in two different final states: st1 = (Y !-> 1 ; X !-> 1) st2 = (Y !-> 2 ; X !-> 1). *) remember (Y !-> 1 ; X !-> 1) as st1. remember (Y !-> 2 ; X !-> 1) as st2. assert (H1 : empty_st =[ c1 ]=> st1); assert (H2 : empty_st =[ c2 ]=> st2); try (subst; apply E_Seq with (st' := (X !-> 1)); apply E_Ass; reflexivity). clear Heqc1 Heqc2. apply H in H1. clear H. (* Finally, we use the fact that evaluation is deterministic to obtain a contradiction. *) assert (Hcontra : st1 = st2) by (apply (ceval_deterministic c2 empty_st); assumption). clear H1 H2. assert (Hcontra' : st1 Y = st2 Y) by (rewrite Hcontra; reflexivity). subst. discriminate. Qed.