(** Formal Reasoning About Programs * An entailment procedure for separation logic's assertion language * Author: Adam Chlipala * License: https://creativecommons.org/licenses/by-nc-nd/4.0/ *) Require Import Frap Setoid Classes.Morphisms. Set Implicit Arguments. Module Type SEP. Parameter hprop : Type. Parameter lift : Prop -> hprop. Parameter star : hprop -> hprop -> hprop. Parameter exis : forall A, (A -> hprop) -> hprop. Notation "[| P |]" := (lift P). Infix "*" := star. Notation "'exists' x .. y , p" := (exis (fun x => .. (exis (fun y => p)) ..)). Parameters himp heq : hprop -> hprop -> Prop. Infix "===" := heq (no associativity, at level 70). Infix "===>" := himp (no associativity, at level 70). Axiom himp_heq : forall p q, p === q <-> (p ===> q /\ q ===> p). Axiom himp_refl : forall p, p ===> p. Axiom himp_trans : forall p q r, p ===> q -> q ===> r -> p ===> r. Axiom lift_left : forall p (Q : Prop) r, (Q -> p ===> r) -> p * [| Q |] ===> r. Axiom lift_right : forall p q (R : Prop), p ===> q -> R -> p ===> q * [| R |]. Axiom extra_lift : forall (P : Prop) p, P -> p === [| P |] * p. Axiom star_comm : forall p q, p * q === q * p. Axiom star_assoc : forall p q r, p * (q * r) === (p * q) * r. Axiom star_cancel : forall p1 p2 q1 q2, p1 ===> p2 -> q1 ===> q2 -> p1 * q1 ===> p2 * q2. Axiom exis_gulp : forall A p (q : A -> _), p * exis q === exis (fun x => p * q x). Axiom exis_left : forall A (p : A -> _) q, (forall x, p x ===> q) -> exis p ===> q. Axiom exis_right : forall A p (q : A -> _) x, p ===> q x -> p ===> exis q. End SEP. Module Make(Import S : SEP). Add Parametric Relation : hprop himp reflexivity proved by himp_refl transitivity proved by himp_trans as himp_rel. Lemma heq_refl : forall p, p === p. Proof. intros; apply himp_heq; intuition (apply himp_refl). Qed. Lemma heq_sym : forall p q, p === q -> q === p. Proof. intros; apply himp_heq; apply himp_heq in H; intuition. Qed. Lemma heq_trans : forall p q r, p === q -> q === r -> p === r. Proof. intros; apply himp_heq; apply himp_heq in H; apply himp_heq in H0; intuition (eauto using himp_trans). Qed. Add Parametric Relation : hprop heq reflexivity proved by heq_refl symmetry proved by heq_sym transitivity proved by heq_trans as heq_rel. Global Instance himp_heq_mor : Proper (heq ==> heq ==> iff) himp. Proof. hnf; intros; hnf; intros. apply himp_heq in H; apply himp_heq in H0. intuition eauto using himp_trans. Qed. Add Parametric Morphism : star with signature heq ==> heq ==> heq as star_mor. Proof. intros; apply himp_heq; apply himp_heq in H; apply himp_heq in H0; intuition (auto using star_cancel). Qed. Add Parametric Morphism : star with signature himp ==> himp ==> himp as star_mor'. Proof. auto using star_cancel. Qed. Global Instance exis_iff_morphism (A : Type) : Proper (pointwise_relation A heq ==> heq) (@exis A). Proof. hnf; intros; apply himp_heq; intuition. hnf in H. apply exis_left; intro. eapply exis_right. assert (x x0 === y x0) by eauto. apply himp_heq in H0; intuition eauto. hnf in H. apply exis_left; intro. eapply exis_right. assert (x x0 === y x0) by eauto. apply himp_heq in H0; intuition eauto. Qed. Global Instance exis_imp_morphism (A : Type) : Proper (pointwise_relation A himp ==> himp) (@exis A). Proof. hnf; intros. apply exis_left; intro. eapply exis_right. unfold pointwise_relation in H. eauto. Qed. Lemma star_combine_lift1 : forall P Q, [| P |] * [| Q |] ===> [| P /\ Q |]. Proof. intros. apply lift_left; intro. rewrite extra_lift with (P := True); auto. apply lift_left; intro. rewrite extra_lift with (P := True) (p := [| P /\ Q |]); auto. apply lift_right. reflexivity. tauto. Qed. Lemma star_combine_lift2 : forall P Q, [| P /\ Q |] ===> [| P |] * [| Q |]. Proof. intros. rewrite extra_lift with (P := True); auto. apply lift_left; intro. apply lift_right; try tauto. rewrite extra_lift with (P := True) (p := [| P |]); auto. apply lift_right; try tauto. reflexivity. Qed. Lemma star_combine_lift : forall P Q, [| P |] * [| Q |] === [| P /\ Q |]. Proof. intros. apply himp_heq; auto using star_combine_lift1, star_combine_lift2. Qed. Lemma star_comm_lift : forall P q, [| P |] * q === q * [| P |]. Proof. intros; apply star_comm. Qed. Lemma star_assoc_lift : forall p Q r, (p * [| Q |]) * r === p * r * [| Q |]. Proof. intros. rewrite <- star_assoc. rewrite (star_comm [| Q |]). apply star_assoc. Qed. Lemma star_comm_exis : forall A (p : A -> _) q, exis p * q === q * exis p. Proof. intros; apply star_comm. Qed. Ltac lift := intros; apply himp_heq; split; repeat (apply lift_left; intro); repeat (apply lift_right; intuition). Lemma lift_combine : forall p Q R, p * [| Q |] * [| R |] === p * [| Q /\ R |]. Proof. intros; apply himp_heq; split; repeat (apply lift_left; intro); repeat (apply lift_right; intuition). Qed. Lemma lift1_left : forall (P : Prop) q, (P -> [| True |] ===> q) -> [| P |] ===> q. Proof. intros. rewrite (@extra_lift True [| P |]); auto. apply lift_left; auto. Qed. Lemma lift1_right : forall p (Q : Prop), Q -> p ===> [| True |] -> p ===> [| Q |]. Proof. intros. rewrite (@extra_lift True [| Q |]); auto. apply lift_right; auto. Qed. Ltac normalize0 := match goal with | [ |- context[star ?p (exis ?q)] ] => rewrite (exis_gulp p q) | [ |- context[star (star ?p (lift ?q)) (lift ?r)] ] => rewrite (lift_combine p q r) | [ |- context[star ?p (star ?q ?r)] ] => rewrite (star_assoc p q r) | [ |- context[star (lift ?p) (lift ?q)] ] => rewrite (star_combine_lift p q) | [ |- context[star (lift ?p) ?q ] ] => rewrite (star_comm_lift p q) | [ |- context[star (star ?p (lift ?q)) ?r] ] => rewrite (star_assoc_lift p q r) | [ |- context[star (exis ?p) ?q] ] => rewrite (star_comm_exis p q) end. Ltac normalizeL := (apply exis_left || apply lift_left; intro; try congruence) || match goal with | [ |- lift ?P ===> _ ] => match P with | True => fail 1 | _ => apply lift1_left; intro; try congruence end end. Ltac normalizeR := match goal with | [ |- _ ===> exis _ ] => eapply exis_right | [ |- _ ===> _ * lift _ ] => apply lift_right | [ |- _ ===> lift ?Q ] => match Q with | True => fail 1 | _ => apply lift1_right end end. Ltac normalize1 := normalize0 || normalizeL || normalizeR. Lemma lift_uncombine : forall p P Q, p * [| P /\ Q |] === p * [| P |] * [| Q |]. Proof. lift. Qed. Ltac normalize2 := match goal with | [ |- context[star ?p (lift (?P /\ ?Q))] ] => rewrite (lift_uncombine p P Q) | [ |- context[star ?p (star ?q ?r)] ] => rewrite (star_assoc p q r) end. Ltac normalizeLeft := let s := fresh "s" in intro s; let rhs := fresh "rhs" in match goal with | [ |- _ ===> ?Q ] => set (rhs := Q) end; simpl; intros; repeat (normalize0 || normalizeL); repeat match goal with | [ H : ex _ |- _ ===> _ ] => destruct H | [ H : _ /\ _ |- _ ] => destruct H | [ H : _ = _ |- _ ] => rewrite H end; subst rhs. Ltac normalize := simpl; intros; repeat normalize1; repeat normalize2; repeat (match goal with | [ H : ex _ |- _ ===> _ ] => destruct H end; intuition idtac). Ltac forAllAtoms p k := match p with | ?q * ?r => forAllAtoms q k || forAllAtoms r k | _ => k p end. Lemma stb1 : forall p q r, (q * p) * r === q * r * p. Proof. intros; rewrite <- star_assoc; rewrite (star_comm p r); apply star_assoc. Qed. Ltac sendToBack part := repeat (rewrite (stb1 part) || rewrite (star_comm part)). Theorem star_cancel' : forall p1 p2 q, p1 ===> p2 -> p1 * q ===> p2 * q. Proof. intros; apply star_cancel; auto using himp_refl. Qed. Theorem star_cancel'' : forall p q, lift True ===> q -> p ===> p * q. Proof. intros. eapply himp_trans. rewrite extra_lift with (P := True); auto. instantiate (1 := p * q). rewrite star_comm. apply star_cancel; auto. reflexivity. reflexivity. Qed. Module Type TRY_ME_FIRST. Parameter try_me_first : hprop -> Prop. Axiom try_me_first_easy : forall p, try_me_first p. End TRY_ME_FIRST. Module TMF : TRY_ME_FIRST. Definition try_me_first (_ : hprop) := True. Theorem try_me_first_easy : forall p, try_me_first p. Proof. constructor. Qed. End TMF. Import TMF. Export TMF. Ltac cancel1 := match goal with | [ |- ?p ===> ?q ] => (is_var q; fail 2) || forAllAtoms p ltac:(fun p0 => (let H := fresh in assert (H : try_me_first p0) by eauto; clear H); sendToBack p0; forAllAtoms q ltac:(fun q0 => (let H := fresh in assert (H : try_me_first q0) by eauto; clear H); sendToBack q0; apply star_cancel')) end || match goal with | [ |- _ ===> ?Q ] => match Q with | _ => is_evar Q; fail 1 | ?Q _ => is_evar Q; fail 1 | _ => apply himp_refl end | [ |- ?p ===> ?q ] => (is_var q; fail 2) || forAllAtoms p ltac:(fun p0 => sendToBack p0; forAllAtoms q ltac:(fun q0 => sendToBack q0; apply star_cancel')) | _ => progress autorewrite with core end. Ltac hide_evars := repeat match goal with | [ |- ?P ===> _ ] => is_evar P; set P | [ |- _ ===> ?Q ] => is_evar Q; set Q | [ |- context[star ?P _] ] => is_evar P; set P | [ |- context[star _ ?Q] ] => is_evar Q; set Q | [ |- _ ===> exists v, _ * ?R v ] => is_evar R; set R end. Ltac restore_evars := repeat match goal with | [ x := _ |- _ ] => subst x end. Fixpoint flattenAnds (Ps : list Prop) : Prop := match Ps with | nil => True | [P] => P | P :: Ps => P /\ flattenAnds Ps end. Ltac allPuresFrom k := match goal with | [ H : ?P |- _ ] => match type of P with | Prop => generalize dependent H; allPuresFrom ltac:(fun Ps => k (P :: Ps)) end | _ => intros; k (@nil Prop) end. Ltac whichToQuantify skip foundAlready k := match goal with | [ x : ?T |- _ ] => match type of T with | Prop => fail 1 | _ => match skip with | context[x] => fail 1 | _ => match foundAlready with | context[x] => fail 1 | _ => (instantiate (1 := lift (x = x)); fail 2) || (instantiate (1 := fun _ => lift (x = x)); fail 2) || (whichToQuantify skip (x, foundAlready) k) end end end | _ => k foundAlready end. Ltac quantifyOverThem vars e k := match vars with | tt => k e | (?x, ?vars') => match e with | context[x] => match eval pattern x in e with | ?f _ => quantifyOverThem vars' (exis f) k end | _ => quantifyOverThem vars' e k end end. Ltac addQuantifiers P k := whichToQuantify tt tt ltac:(fun vars => quantifyOverThem vars P k). Ltac addQuantifiersSkipping x P k := whichToQuantify x tt ltac:(fun vars => quantifyOverThem vars P k). Ltac basic_cancel := normalize; repeat cancel1; repeat match goal with | [ H : _ /\ _ |- _ ] => destruct H | [ |- _ /\ _ ] => split end; eassumption || apply I. Ltac beautify := repeat match goal with | [ H : True |- _ ] => clear H | [ H : ?P, H' : ?P |- _ ] => match type of P with | Prop => clear H' end | [ H : _ /\ _ |- _ ] => destruct H end. Ltac cancel := hide_evars; normalize; repeat cancel1; restore_evars; beautify; try match goal with | [ |- _ ===> ?p * ?q ] => ((is_evar p; fail 1) || apply star_cancel'') || ((is_evar q; fail 1) || (rewrite (star_comm p q); apply star_cancel'')) end; try match goal with | [ |- ?P ===> _ ] => sendToBack P; match goal with | [ |- ?P ===> ?Q * ?P ] => is_evar Q; rewrite (star_comm Q P); allPuresFrom ltac:(fun Ps => match Ps with | nil => instantiate (1 := lift True) | _ => let Ps' := eval simpl in (flattenAnds Ps) in addQuantifiers (lift Ps') ltac:(fun e => instantiate (1 := e)) end; basic_cancel) end | [ |- ?P ===> ?Q ] => is_evar Q; allPuresFrom ltac:(fun Ps => match Ps with | nil => reflexivity | _ => let Ps' := eval simpl in (flattenAnds Ps) in addQuantifiers (star P (lift Ps')) ltac:(fun e => instantiate (1 := e)); basic_cancel end) | [ |- ?P ===> ?Q ?x ] => is_evar Q; allPuresFrom ltac:(fun Ps => match Ps with | nil => reflexivity | _ => let Ps' := eval simpl in (flattenAnds Ps) in addQuantifiersSkipping x (star P (lift Ps')) ltac:(fun e => match eval pattern x in e with | ?f _ => instantiate (1 := f) end); basic_cancel end) | [ |- _ ===> _ ] => intuition (try congruence) end; intuition idtac; beautify. End Make.