From ece1ecdd5efa031b398d12940d5587f65633d451 Mon Sep 17 00:00:00 2001
From: Robbert Krebbers <mail@robbertkrebbers.nl>
Date: Wed, 13 Mar 2019 15:43:02 +0100
Subject: [PATCH] Get rid of locked value lambdas.

---
 HeapLang.md                     | 11 ++++-------
 theories/heap_lang/lib/assert.v |  7 ++++---
 theories/heap_lang/lib/par.v    |  2 +-
 theories/heap_lang/lifting.v    | 28 ++++++++++++++++++----------
 theories/heap_lang/notation.v   | 15 ++-------------
 theories/heap_lang/proofmode.v  | 17 ++++++++---------
 6 files changed, 37 insertions(+), 43 deletions(-)

diff --git a/HeapLang.md b/HeapLang.md
index 17c5e144b..48a52fac2 100644
--- a/HeapLang.md
+++ b/HeapLang.md
@@ -42,10 +42,6 @@ We define a whole lot of short-hands, such as non-recursive functions (`λ:`),
 let-bindings, sequential composition, and a more conventional `match:` that has
 binders in both branches.
 
-Noteworthy is the fact that functions (`rec:`, `λ:`) in the value scope (`%V`)
-are *locked*.  This is to prevent them from being unfolded and reduced too
-eagerly.
-
 The widely used `#` is a short-hand to turn a basic literal (an integer, a
 location, a boolean literal or a unit value) into a value.  Since values coerce
 to expressions, `#` is widely used whenever a Coq value needs to be placed into
@@ -62,9 +58,10 @@ Tactics to take one or more pure program steps:
 - `wp_pure`: Perform one pure reduction step.  Pure steps are defined by the
   `PureExec` typeclass and include beta reduction, projections, constructors, as
   well as unary and binary arithmetic operators.
-- `wp_pures`: Perform as many pure reduction steps as possible.
+- `wp_pures`: Perform as many pure reduction steps as possible. This
+  tactic will **not** reduce lambdas/recs that are hidden behind a definition.
 - `wp_rec`, `wp_lam`: Perform a beta reduction.  Unlike `wp_pure`, this will
-  also reduce locked lambdas.
+  also reduce lambdas that are hidden behind a definition.
 - `wp_let`, `wp_seq`: Reduce a let-binding or a sequential composition.
 - `wp_proj`: Reduce a projection.
 - `wp_if_true`, `wp_if_false`, `wp_if`: Reduce a conditional expression. The
@@ -122,7 +119,7 @@ The normal `e1 ||| e2` notation uses expression lambdas, because clearly we want
 value lambda).  However, the *specification* for parallel composition should use
 value lambdas, because prior to applying it the term will be reduced as much as
 possible to achieve a normal form.  To facilitate this, we define a copy of the
-`e1 ||| e2` notation in the value scope that uses *unlocked* value lambdas.
+`e1 ||| e2` notation in the value scope that uses value lambdas.
 This is not actually a value, but we still but it in the value scope to
 differentiate from the other notation that uses expression lambdas.  (In the
 future, we might decide to add a separate scope for this.)  Then, we write the
diff --git a/theories/heap_lang/lib/assert.v b/theories/heap_lang/lib/assert.v
index d1d6513d3..04a6d7613 100644
--- a/theories/heap_lang/lib/assert.v
+++ b/theories/heap_lang/lib/assert.v
@@ -7,11 +7,12 @@ Set Default Proof Using "Type".
 Definition assert : val :=
   λ: "v", if: "v" #() then #() else #0 #0. (* #0 #0 is unsafe *)
 (* just below ;; *)
-Notation "'assert:' e" := (assert (λ: <>, e))%E (at level 99) : expr_scope.
+Notation "'assert:' e" := (assert (λ: <>, e)%E) (at level 99) : expr_scope.
+Notation "'assert:' e" := (assert (λ: <>, e)%V) (at level 99) : val_scope.
 
 Lemma twp_assert `{!heapG Σ} E (Φ : val → iProp Σ) e :
   WP e @ E [{ v, ⌜v = #true⌝ ∧ Φ #() }] -∗
-  WP assert (LamV BAnon e)%V @ E [{ Φ }].
+  WP (assert: e)%V @ E [{ Φ }].
 Proof.
   iIntros "HΦ". wp_lam.
   wp_apply (twp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
@@ -19,7 +20,7 @@ Qed.
 
 Lemma wp_assert `{!heapG Σ} E (Φ : val → iProp Σ) e :
   WP e @ E {{ v, ⌜v = #true⌝ ∧ ▷ Φ #() }} -∗
-  WP assert (LamV BAnon e)%V @ E {{ Φ }}.
+  WP (assert: e)%V @ E {{ Φ }}.
 Proof.
   iIntros "HΦ". wp_lam.
   wp_apply (wp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
diff --git a/theories/heap_lang/lib/par.v b/theories/heap_lang/lib/par.v
index d073142e2..558853f4f 100644
--- a/theories/heap_lang/lib/par.v
+++ b/theories/heap_lang/lib/par.v
@@ -12,7 +12,7 @@ Definition par : val :=
     let: "v1" := join "handle" in
     ("v1", "v2").
 Notation "e1 ||| e2" := (par (λ: <>, e1)%E (λ: <>, e2)%E) : expr_scope.
-Notation "e1 ||| e2" := (par (LamV BAnon e1%E) (LamV BAnon e2%E)) : val_scope.
+Notation "e1 ||| e2" := (par (λ: <>, e1)%V (λ: <>, e2)%V) : val_scope.
 
 Section proof.
 Local Set Default Proof Using "Type*".
diff --git a/theories/heap_lang/lifting.v b/theories/heap_lang/lifting.v
index 17571589b..4d3b63267 100644
--- a/theories/heap_lang/lifting.v
+++ b/theories/heap_lang/lifting.v
@@ -91,18 +91,26 @@ Local Ltac solve_pure_exec :=
   subst; intros ?; apply nsteps_once, pure_head_step_pure_step;
     constructor; [solve_exec_safe | solve_exec_puredet].
 
+(** The behavior of the various [wp_] tactics with regard to lambda differs in
+the following way:
+
+- [wp_pures] does *not* reduce lambdas/recs that are hidden behind a definition.
+- [wp_rec] and [wp_lam] reduce lambdas/recs that are hidden behind a definition.
+
+To realize this behavior, we define the class [AsRecV v f x erec], which takes a
+value [v] as its input, and turns it into a [RecV f x erec] via the instance
+[AsRecV_recv : AsRecV (RecV f x e) f x e]. We register this instance via
+[Hint Extern] so that it is only used if [v] is syntactically a lambda/rec, and
+not if [v] contains a lambda/rec that is hidden behind a definition.
+
+To make sure that [wp_rec] and [wp_lam] do reduce lambdas/recs that are hidden
+behind a definition, we activate [AsRecV_recv] by hand in these tactics. *)
 Class AsRecV (v : val) (f x : binder) (erec : expr) :=
   as_recv : v = RecV f x erec.
-Instance AsRecV_recv f x e : AsRecV (RecV f x e) f x e := eq_refl.
-
-(* Pure reductions are automatically performed before any wp_ tactics
-   handling impure operations. Since we do not want these tactics to
-   unfold locked terms, we do not register this instance explicitely,
-   but only activate it by hand in the `wp_rec` tactic, where we
-   *actually* want it to unlock. *)
-Lemma AsRecV_recv_locked v f x e :
-  AsRecV v f x e → AsRecV (locked v) f x e.
-Proof. by unlock. Qed.
+Hint Mode AsRecV ! - - - : typeclass_instances.
+Definition AsRecV_recv f x e : AsRecV (RecV f x e) f x e := eq_refl.
+Hint Extern 0 (AsRecV (RecV _ _ _) _ _ _) =>
+  apply AsRecV_recv : typeclass_instances.
 
 Instance pure_recc f x (erec : expr) :
   PureExec True 1 (Rec f x erec) (Val $ RecV f x erec).
diff --git a/theories/heap_lang/notation.v b/theories/heap_lang/notation.v
index dc3fda17d..c5b85612b 100644
--- a/theories/heap_lang/notation.v
+++ b/theories/heap_lang/notation.v
@@ -85,7 +85,7 @@ by two spaces in case the whole rec does not fit on a single line. *)
 Notation "'rec:' f x := e" := (Rec f%bind x%bind e%E)
   (at level 200, f at level 1, x at level 1, e at level 200,
    format "'[' 'rec:'  f  x  :=  '/  ' e ']'") : expr_scope.
-Notation "'rec:' f x := e" := (locked (RecV f%bind x%bind e%E))
+Notation "'rec:' f x := e" := (RecV f%bind x%bind e%E)
   (at level 200, f at level 1, x at level 1, e at level 200,
    format "'[' 'rec:'  f  x  :=  '/  ' e ']'") : val_scope.
 Notation "'if:' e1 'then' e2 'else' e3" := (If e1%E e2%E e3%E)
@@ -98,7 +98,7 @@ notations are otherwise not pretty printed back accordingly. *)
 Notation "'rec:' f x y .. z := e" := (Rec f%bind x%bind (Lam y%bind .. (Lam z%bind e%E) ..))
   (at level 200, f, x, y, z at level 1, e at level 200,
    format "'[' 'rec:'  f  x  y  ..  z  :=  '/  ' e ']'") : expr_scope.
-Notation "'rec:' f x y .. z := e" := (locked (RecV f%bind x%bind (Lam y%bind .. (Lam z%bind e%E) ..)))
+Notation "'rec:' f x y .. z := e" := (RecV f%bind x%bind (Lam y%bind .. (Lam z%bind e%E) ..))
   (at level 200, f, x, y, z at level 1, e at level 200,
    format "'[' 'rec:'  f  x  y  ..  z  :=  '/  ' e ']'") : val_scope.
 
@@ -111,21 +111,10 @@ Notation "λ: x y .. z , e" := (Lam x%bind (Lam y%bind .. (Lam z%bind e%E) ..))
   (at level 200, x, y, z at level 1, e at level 200,
    format "'[' 'λ:'  x  y  ..  z ,  '/  ' e ']'") : expr_scope.
 
-(* When parsing lambdas, we want them to be locked (so as to avoid needless
-unfolding by tactics and unification). However, unlocked lambda-values sometimes
-appear as part of compound expressions, in which case we want them to be pretty
-printed too. We achieve that by using printing only notations for the non-locked
-notation. *)
 Notation "λ: x , e" := (LamV x%bind e%E)
-  (at level 200, x at level 1, e at level 200,
-   format "'[' 'λ:'  x ,  '/  ' e ']'", only printing) : val_scope.
-Notation "λ: x , e" := (locked (LamV x%bind e%E))
   (at level 200, x at level 1, e at level 200,
    format "'[' 'λ:'  x ,  '/  ' e ']'") : val_scope.
 Notation "λ: x y .. z , e" := (LamV x%bind (Lam y%bind .. (Lam z%bind e%E) .. ))
-  (at level 200, x, y, z at level 1, e at level 200,
-   format "'[' 'λ:'  x  y  ..  z ,  '/  ' e ']'", only printing) : val_scope.
-Notation "λ: x y .. z , e" := (locked (LamV x%bind (Lam y%bind .. (Lam z%bind e%E) .. )))
   (at level 200, x, y, z at level 1, e at level 200,
    format "'[' 'λ:'  x  y  ..  z ,  '/  ' e ']'") : val_scope.
 
diff --git a/theories/heap_lang/proofmode.v b/theories/heap_lang/proofmode.v
index 2f718e7f6..8519e3ec9 100644
--- a/theories/heap_lang/proofmode.v
+++ b/theories/heap_lang/proofmode.v
@@ -105,17 +105,16 @@ Ltac wp_pures :=
   repeat (wp_pure _; []). (* The `;[]` makes sure that no side-condition
                              magically spawns. *)
 
-(* The handling of beta-reductions with wp_rec needs special care in
-  order to allow it to unlock locked `RecV` values: We first put
-  `AsRecV_recv_locked` in the current environment so that it can be
-  used as an instance by the typeclass resolution system, then we
-  perform the reduction, and finally we clear this new hypothesis.
-
-  The reason is that we do not want impure wp_ tactics to unfold
-  locked terms, while we want them to execute arbitrary pure steps. *)
+(** Unlike [wp_pures], the tactics [wp_rec] and [wp_lam] should also reduce
+lambdas/recs that are hidden behind a definition, i.e. they should use
+[AsRecV_recv] as a proper instance instead of a [Hint Extern].
+
+We achieve this by putting [AsRecV_recv] in the current environment so that it
+can be used as an instance by the typeclass resolution system. We then perform
+the reduction, and finally we clear this new hypothesis. *)
 Tactic Notation "wp_rec" :=
   let H := fresh in
-  assert (H := AsRecV_recv_locked);
+  assert (H := AsRecV_recv);
   wp_pure (App _ _);
   clear H.
 
-- 
GitLab