From 32410f1ed1987cf94bd27411439ac57dc0f94d12 Mon Sep 17 00:00:00 2001
From: Robbert Krebbers <mail@robbertkrebbers.nl>
Date: Fri, 12 Feb 2016 02:23:44 +0100
Subject: [PATCH] Document gsubst function and move to separate file.

---
 _CoqProject              |   1 +
 heap_lang/derived.v      |  12 ++--
 heap_lang/lifting.v      |  96 ++++---------------------
 heap_lang/substitution.v | 147 +++++++++++++++++++++++++++++++++++++++
 heap_lang/tests.v        |  27 +++----
 5 files changed, 178 insertions(+), 105 deletions(-)
 create mode 100644 heap_lang/substitution.v

diff --git a/_CoqProject b/_CoqProject
index 094baa0ed..354725716 100644
--- a/_CoqProject
+++ b/_CoqProject
@@ -72,3 +72,4 @@ heap_lang/lifting.v
 heap_lang/derived.v
 heap_lang/notation.v
 heap_lang/tests.v
+heap_lang/substitution.v
diff --git a/heap_lang/derived.v b/heap_lang/derived.v
index eadeb450a..a8fd8ea2e 100644
--- a/heap_lang/derived.v
+++ b/heap_lang/derived.v
@@ -16,20 +16,17 @@ Implicit Types Q : val → iProp heap_lang Σ.
 
 (** Proof rules for the sugar *)
 Lemma wp_lam E x ef e v Q :
-  to_val e = Some v → ▷ wp E (gsubst ef x e) Q ⊑ wp E (App (Lam x ef) e) Q.
-Proof.
-  intros <-%of_to_val; rewrite -wp_rec ?to_of_val //.
-  by rewrite (gsubst_correct _ _ (RecV _ _ _)) subst_empty.
-Qed.
+  to_val e = Some v → ▷ wp E (subst ef x v) Q ⊑ wp E (App (Lam x ef) e) Q.
+Proof. intros. by rewrite -wp_rec ?subst_empty. Qed.
 
 Lemma wp_let E x e1 e2 v Q :
-  to_val e1 = Some v → ▷ wp E (gsubst e2 x e1) Q ⊑ wp E (Let x e1 e2) Q.
+  to_val e1 = Some v → ▷ wp E (subst e2 x v) Q ⊑ wp E (Let x e1 e2) Q.
 Proof. apply wp_lam. Qed.
 
 Lemma wp_seq E e1 e2 Q : wp E e1 (λ _, ▷ wp E e2 Q) ⊑ wp E (Seq e1 e2) Q.
 Proof.
   rewrite -(wp_bind [LetCtx "" e2]). apply wp_mono=>v.
-  by rewrite -wp_let //= ?gsubst_correct ?subst_empty ?to_of_val.
+  by rewrite -wp_let //= ?to_of_val ?subst_empty.
 Qed.
 
 Lemma wp_le E (n1 n2 : Z) P Q :
@@ -58,5 +55,4 @@ Proof.
   intros. rewrite -wp_bin_op //; [].
   destruct (bool_decide_reflect (n1 = n2)); by eauto with omega.
 Qed.
-
 End derived.
diff --git a/heap_lang/lifting.v b/heap_lang/lifting.v
index 068d8237d..7de00c2cb 100644
--- a/heap_lang/lifting.v
+++ b/heap_lang/lifting.v
@@ -1,61 +1,11 @@
-Require Import prelude.gmap program_logic.lifting program_logic.ownership.
-Require Export program_logic.weakestpre heap_lang.heap_lang_tactics.
+Require Export program_logic.weakestpre heap_lang.heap_lang.
+Require Import program_logic.lifting.
+Require Import program_logic.ownership. (* for ownP *)
+Require Import heap_lang.heap_lang_tactics.
 Export heap_lang. (* Prefer heap_lang names over language names. *)
 Import uPred.
 Local Hint Extern 0 (language.reducible _ _) => do_step ltac:(eauto 2).
 
-(** The substitution operation [gsubst e x ev] behaves just as [subst] but
-in case [e] does not contain the free variable [x] it will return [e] in a
-way that is syntactically equal (i.e. without any Coq-level delta reduction
-performed) *)
-Arguments of_val : simpl never.
-Definition gsubst_lift {A B C} (f : A → B → C)
-    (x : A) (y : B) (mx : option A) (my : option B) : option C :=
-  match mx, my with
-  | Some x, Some y => Some (f x y)
-  | Some x, None => Some (f x y)
-  | None, Some y => Some (f x y)
-  | None, None => None
-  end.
-Fixpoint gsubst_go (e : expr) (x : string) (ev : expr) : option expr :=
-  match e with
-  | Var y => if decide (x = y ∧ x ≠ "") then Some ev else None
-  | Rec f y e =>
-     if decide (x ≠ f ∧ x ≠ y) then Rec f y <$> gsubst_go e x ev else None
-  | App e1 e2 => gsubst_lift App e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
-  | Lit l => None
-  | UnOp op e => UnOp op <$> gsubst_go e x ev
-  | BinOp op e1 e2 =>
-     gsubst_lift (BinOp op) e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
-  | If e0 e1 e2 =>
-     gsubst_lift id (If e0 e1) e2
-       (gsubst_lift If e0 e1 (gsubst_go e0 x ev) (gsubst_go e1 x ev))
-       (gsubst_go e2 x ev)
-  | Pair e1 e2 => gsubst_lift Pair e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
-  | Fst e => Fst <$> gsubst_go e x ev
-  | Snd e => Snd <$> gsubst_go e x ev
-  | InjL e => InjL <$> gsubst_go e x ev
-  | InjR e => InjR <$> gsubst_go e x ev
-  | Case e0 x1 e1 x2 e2 =>
-     gsubst_lift id (Case e0 x1 e1 x2) e2
-       (gsubst_lift (λ e0' e1', Case e0' x1 e1' x2) e0 e1
-         (gsubst_go e0 x ev)
-         (if decide (x ≠ x1) then gsubst_go e1 x ev else None))
-       (if decide (x ≠ x2) then gsubst_go e2 x ev else None)
-  | Fork e => Fork <$> gsubst_go e x ev
-  | Loc l => None
-  | Alloc e => Alloc <$> gsubst_go e x ev
-  | Load e => Load <$> gsubst_go e x ev
-  | Store e1 e2 => gsubst_lift Store e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
-  | Cas e0 e1 e2 =>
-     gsubst_lift id (Cas e0 e1) e2
-       (gsubst_lift Cas e0 e1 (gsubst_go e0 x ev) (gsubst_go e1 x ev))
-       (gsubst_go e2 x ev)
-  end.
-Definition gsubst (e : expr) (x : string) (ev : expr) : expr :=
-  from_option e (gsubst_go e x ev).
-Arguments gsubst !_ _ _/.
-
 Section lifting.
 Context {Σ : iFunctor}.
 Implicit Types P : iProp heap_lang Σ.
@@ -63,20 +13,6 @@ Implicit Types Q : val → iProp heap_lang Σ.
 Implicit Types K : ectx.
 Implicit Types ef : option expr.
 
-Lemma gsubst_None e x v : gsubst_go e x (of_val v) = None → e = subst e x v.
-Proof.
-  induction e; simpl; unfold gsubst_lift; intros;
-    repeat (simplify_option_equality || case_match); f_equal; auto.
-Qed.
-Lemma gsubst_correct e x v : gsubst e x (of_val v) = subst e x v.
-Proof.
-  unfold gsubst; destruct (gsubst_go e x (of_val v)) as [e'|] eqn:He; simpl;
-    last by apply gsubst_None.
-  revert e' He; induction e; simpl; unfold gsubst_lift; intros;
-    repeat (simplify_option_equality || case_match);
-    f_equal; auto using gsubst_None.
-Qed.
-
 (** Bind. *)
 Lemma wp_bind {E e} K Q :
   wp E e (λ v, wp E (fill K (of_val v)) Q) ⊑ wp E (fill K e) Q.
@@ -150,18 +86,12 @@ Qed.
 
 Lemma wp_rec E f x e1 e2 v Q :
   to_val e2 = Some v →
-  ▷ wp E (gsubst (gsubst e1 f (Rec f x e1)) x e2) Q ⊑ wp E (App (Rec f x e1) e2) Q.
+  ▷ wp E (subst (subst e1 f (RecV f x e1)) x v) Q ⊑ wp E (App (Rec f x e1) e2) Q.
 Proof.
-  intros <-%of_to_val; rewrite gsubst_correct (gsubst_correct _ _ (RecV _ _ _)).
-  rewrite -(wp_lift_pure_det_step (App _ _)
+  intros. rewrite -(wp_lift_pure_det_step (App _ _)
     (subst (subst e1 f (RecV f x e1)) x v) None) ?right_id //=;
     intros; inv_step; eauto.
 Qed.
-Lemma wp_rec' E e1 f x erec e2 v Q :
-  e1 = Rec f x erec →
-  to_val e2 = Some v →
-  ▷ wp E (gsubst (gsubst erec f e1) x e2) Q ⊑ wp E (App e1 e2) Q.
-Proof. intros ->; apply wp_rec. Qed.
 
 Lemma wp_un_op E op l l' Q :
   un_op_eval op l = Some l' →
@@ -209,20 +139,18 @@ Qed.
 
 Lemma wp_case_inl E e0 v0 x1 e1 x2 e2 Q :
   to_val e0 = Some v0 →
-  ▷ wp E (gsubst e1 x1 e0) Q ⊑ wp E (Case (InjL e0) x1 e1 x2 e2) Q.
+  ▷ wp E (subst e1 x1 v0) Q ⊑ wp E (Case (InjL e0) x1 e1 x2 e2) Q.
 Proof.
-  intros <-%of_to_val; rewrite gsubst_correct.
-  rewrite -(wp_lift_pure_det_step (Case _ _ _ _ _) (subst e1 x1 v0) None)
-    ?right_id //; intros; inv_step; eauto.
+  intros. rewrite -(wp_lift_pure_det_step (Case _ _ _ _ _)
+    (subst e1 x1 v0) None) ?right_id //; intros; inv_step; eauto.
 Qed.
 
 Lemma wp_case_inr E e0 v0 x1 e1 x2 e2 Q :
   to_val e0 = Some v0 →
-  ▷ wp E (gsubst e2 x2 e0) Q ⊑ wp E (Case (InjR e0) x1 e1 x2 e2) Q.
+  ▷ wp E (subst e2 x2 v0) Q ⊑ wp E (Case (InjR e0) x1 e1 x2 e2) Q.
 Proof.
-  intros <-%of_to_val; rewrite gsubst_correct.
-  rewrite -(wp_lift_pure_det_step (Case _ _ _ _ _) (subst e2 x2 v0) None)
-    ?right_id //; intros; inv_step; eauto.
+  intros. rewrite -(wp_lift_pure_det_step (Case _ _ _ _ _)
+    (subst e2 x2 v0) None) ?right_id //; intros; inv_step; eauto.
 Qed.
 
 End lifting.
diff --git a/heap_lang/substitution.v b/heap_lang/substitution.v
new file mode 100644
index 000000000..2d8ce873f
--- /dev/null
+++ b/heap_lang/substitution.v
@@ -0,0 +1,147 @@
+Require Export heap_lang.derived.
+
+(** We define an alternative notion of substitution [gsubst e x ev] that
+preserves the expression [e] syntactically in case the variable [x] does not
+occur freely in [e]. By syntactically, we mean that [e] is preserved without
+unfolding any Coq definitions. For example:
+
+<<
+  Definition id : val := λ: "x", "x".
+  Eval simpl in subst (id "y") "y" '10.   (* (Lam "x" "x") '10 *)
+  Eval simpl in gsubst (id "y") "y" '10.  (* id '10 *)
+>>
+
+For [gsubst e x ev] to work, [e] should not contain any opaque parts.
+
+The function [gsubst e x ev] differs in yet another way from [subst e x v].
+The function [gsubst] substitutes an expression [ev] whereas [subst]
+substitutes a value [v]. This way we avoid unnecessary conversion back and
+forth between values and expressions, which could also cause Coq definitions
+to be unfolded. For example, consider the rule [wp_rec'] from below:
+
+<<
+  Definition foo : val := rec: "f" "x" := ... .
+
+  Lemma wp_rec' E e1 f x erec e2 v Q :
+    e1 = Rec f x erec →
+    to_val e2 = Some v →
+    ▷ wp E (gsubst (gsubst erec f e1) x e2) Q ⊑ wp E (App e1 e2) Q.
+>>
+
+We ensure that [e1] is substituted instead of [RecV f x erec]. So, for example
+when taking [e1 := foo] we ensure that [foo] is substituted without being
+unfolded. *)
+
+(** * Implementation *)
+(** The core of [gsubst e x ev] is the partial function [gsubst_go e x ev] that
+returns [None] in case [x] does not occur freely in [e] and [Some e'] in case
+[x] occurs in [e]. The function [gsubst e x ev] is then simply defined as
+[from_option e (gsubst_go e x ev)]. *)
+Definition gsubst_lift {A B C} (f : A → B → C)
+    (x : A) (y : B) (mx : option A) (my : option B) : option C :=
+  match mx, my with
+  | Some x, Some y => Some (f x y)
+  | Some x, None => Some (f x y)
+  | None, Some y => Some (f x y)
+  | None, None => None
+  end.
+Fixpoint gsubst_go (e : expr) (x : string) (ev : expr) : option expr :=
+  match e with
+  | Var y => if decide (x = y ∧ x ≠ "") then Some ev else None
+  | Rec f y e =>
+     if decide (x ≠ f ∧ x ≠ y) then Rec f y <$> gsubst_go e x ev else None
+  | App e1 e2 => gsubst_lift App e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
+  | Lit l => None
+  | UnOp op e => UnOp op <$> gsubst_go e x ev
+  | BinOp op e1 e2 =>
+     gsubst_lift (BinOp op) e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
+  | If e0 e1 e2 =>
+     gsubst_lift id (If e0 e1) e2
+       (gsubst_lift If e0 e1 (gsubst_go e0 x ev) (gsubst_go e1 x ev))
+       (gsubst_go e2 x ev)
+  | Pair e1 e2 => gsubst_lift Pair e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
+  | Fst e => Fst <$> gsubst_go e x ev
+  | Snd e => Snd <$> gsubst_go e x ev
+  | InjL e => InjL <$> gsubst_go e x ev
+  | InjR e => InjR <$> gsubst_go e x ev
+  | Case e0 x1 e1 x2 e2 =>
+     gsubst_lift id (Case e0 x1 e1 x2) e2
+       (gsubst_lift (λ e0' e1', Case e0' x1 e1' x2) e0 e1
+         (gsubst_go e0 x ev)
+         (if decide (x ≠ x1) then gsubst_go e1 x ev else None))
+       (if decide (x ≠ x2) then gsubst_go e2 x ev else None)
+  | Fork e => Fork <$> gsubst_go e x ev
+  | Loc l => None
+  | Alloc e => Alloc <$> gsubst_go e x ev
+  | Load e => Load <$> gsubst_go e x ev
+  | Store e1 e2 => gsubst_lift Store e1 e2 (gsubst_go e1 x ev) (gsubst_go e2 x ev)
+  | Cas e0 e1 e2 =>
+     gsubst_lift id (Cas e0 e1) e2
+       (gsubst_lift Cas e0 e1 (gsubst_go e0 x ev) (gsubst_go e1 x ev))
+       (gsubst_go e2 x ev)
+  end.
+Definition gsubst (e : expr) (x : string) (ev : expr) : expr :=
+  from_option e (gsubst_go e x ev).
+
+(** * Simpl  *)
+(** Ensure that [simpl] unfolds [gsubst e x ev] when applied to a concrete term
+[e] and use [simpl nomatch] to ensure that it does not reduce in case [e]
+contains any opaque parts that block reduction. *)
+Arguments gsubst !_ _ _/ : simpl nomatch.
+
+(** We define heap_lang functions as values (see [id] above). In order to
+ensure too eager conversion to expressions, we block [simpl]. *)
+Arguments of_val : simpl never.
+
+(** * Correctness *)
+(** We prove that [gsubst] behaves like [subst] when substituting values. *)
+Lemma gsubst_None e x v : gsubst_go e x (of_val v) = None → e = subst e x v.
+Proof.
+  induction e; simpl; unfold gsubst_lift; intros;
+    repeat (simplify_option_equality || case_match); f_equal; auto.
+Qed.
+Lemma gsubst_correct e x v : gsubst e x (of_val v) = subst e x v.
+Proof.
+  unfold gsubst; destruct (gsubst_go e x (of_val v)) as [e'|] eqn:He; simpl;
+    last by apply gsubst_None.
+  revert e' He; induction e; simpl; unfold gsubst_lift; intros;
+    repeat (simplify_option_equality || case_match);
+    f_equal; auto using gsubst_None.
+Qed.
+
+(** * Weakest precondition rules *)
+Section wp.
+Context {Σ : iFunctor}.
+Implicit Types P : iProp heap_lang Σ.
+Implicit Types Q : val → iProp heap_lang Σ.
+Hint Resolve to_of_val.
+
+Lemma wp_rec' E e1 f x erec e2 v Q :
+  e1 = Rec f x erec →
+  to_val e2 = Some v →
+  ▷ wp E (gsubst (gsubst erec f e1) x e2) Q ⊑ wp E (App e1 e2) Q.
+Proof.
+  intros -> <-%of_to_val.
+  rewrite (gsubst_correct _ _ (RecV _ _ _)) gsubst_correct.
+  by apply wp_rec.
+Qed.
+
+Lemma wp_lam' E x ef e v Q :
+  to_val e = Some v → ▷ wp E (gsubst ef x e) Q ⊑ wp E (App (Lam x ef) e) Q.
+Proof. intros <-%of_to_val; rewrite gsubst_correct. by apply wp_lam. Qed.
+
+Lemma wp_let' E x e1 e2 v Q :
+  to_val e1 = Some v → ▷ wp E (gsubst e2 x e1) Q ⊑ wp E (Let x e1 e2) Q.
+Proof. apply wp_lam'. Qed.
+
+Lemma wp_case_inl' E e0 v0 x1 e1 x2 e2 Q :
+  to_val e0 = Some v0 →
+  ▷ wp E (gsubst e1 x1 e0) Q ⊑ wp E (Case (InjL e0) x1 e1 x2 e2) Q.
+Proof. intros <-%of_to_val; rewrite gsubst_correct. by apply wp_case_inl. Qed.
+
+Lemma wp_case_inr' E e0 v0 x1 e1 x2 e2 Q :
+  to_val e0 = Some v0 →
+  ▷ wp E (gsubst e2 x2 e0) Q ⊑ wp E (Case (InjR e0) x1 e1 x2 e2) Q.
+Proof. intros <-%of_to_val; rewrite gsubst_correct. by apply wp_case_inr. Qed.
+End wp.
+
diff --git a/heap_lang/tests.v b/heap_lang/tests.v
index f99b3a4a6..444172f6a 100644
--- a/heap_lang/tests.v
+++ b/heap_lang/tests.v
@@ -1,6 +1,6 @@
 (** This file is essentially a bunch of testcases. *)
 Require Import program_logic.ownership.
-Require Import heap_lang.notation.
+Require Import heap_lang.notation heap_lang.substitution heap_lang.heap_lang_tactics.
 Import uPred.
 
 Module LangTests.
@@ -59,7 +59,8 @@ Module LiftingTests.
       let: "yp" := "y" + '1 in
       if "yp" < "x" then "pred" "x" "yp" else "y".
   Definition Pred : val :=
-    λ: "x", if "x" ≤ '0 then -FindPred (-"x" + '2) '0 else FindPred "x" '0.
+    λ: "x",
+      if "x" ≤ '0 then -FindPred (-"x" + '2) '0 else FindPred "x" '0.
 
   Lemma FindPred_spec n1 n2 E Q :
     (■ (n1 < n2) ∧ Q '(n2 - 1)) ⊑ wp E (FindPred 'n2 'n1)%L Q.
@@ -73,21 +74,21 @@ Module LiftingTests.
     rewrite ->(later_intro (Q _)).
     rewrite -!later_and; apply later_mono.
     (* Go on *)
-    rewrite -wp_let //= -later_intro.
-    rewrite -(wp_bindi (LetCtx _ _)) -wp_bin_op //= -wp_let //=.
-    rewrite -(wp_bindi (IfCtx _ _)) /= -!later_intro.
+    rewrite -wp_let' //= -later_intro.
+    rewrite -(wp_bindi (LetCtx _ _)) -wp_bin_op //= -wp_let' //= -!later_intro.
+    rewrite -(wp_bindi (IfCtx _ _)) /=.
     apply wp_lt=> ?.
-    - rewrite -wp_if_true.
-      rewrite -!later_intro (forall_elim (n1 + 1)) const_equiv; last omega.
+    - rewrite -wp_if_true -!later_intro.
+      rewrite (forall_elim (n1 + 1)) const_equiv; last omega.
       by rewrite left_id impl_elim_l.
     - assert (n1 = n2 - 1) as -> by omega.
-      rewrite -wp_if_false.
-      by rewrite -!later_intro -wp_value' // and_elim_r.
+      rewrite -wp_if_false -!later_intro.
+      by rewrite -wp_value' // and_elim_r.
   Qed.
 
   Lemma Pred_spec n E Q : ▷ Q (LitV (n - 1)) ⊑ wp E (Pred 'n)%L Q.
   Proof.
-    rewrite -wp_lam //=.
+    rewrite -wp_lam' //=.
     rewrite -(wp_bindi (IfCtx _ _)) /=.
     apply later_mono, wp_le=> Hn.
     - rewrite -wp_if_true.
@@ -99,8 +100,8 @@ Module LiftingTests.
       rewrite -FindPred_spec. apply and_intro; first by (apply const_intro; omega).
       rewrite -wp_un_op //= -later_intro.
       by assert (n - 1 = - (- n + 2 - 1)) as -> by omega.
-    - rewrite -wp_if_false.
-      rewrite -!later_intro -FindPred_spec.
+    - rewrite -wp_if_false -!later_intro.
+      rewrite -FindPred_spec.
       auto using and_intro, const_intro with omega.
   Qed.
 
@@ -108,7 +109,7 @@ Module LiftingTests.
     True ⊑ wp (Σ:=Σ) E (let: "x" := Pred '42 in Pred "x") (λ v, v = '40).
   Proof.
     intros E.
-    rewrite -(wp_bindi (LetCtx _ _)) -Pred_spec //= -wp_let //=.
+    rewrite -(wp_bindi (LetCtx _ _)) -Pred_spec //= -wp_let' //=.
     by rewrite -Pred_spec -!later_intro /=.
   Qed.
 End LiftingTests.
-- 
GitLab