diff git a/algebra/upred_tactics.v b/algebra/upred_tactics.v
index 5e7e2fb2ea6e8a36f0b8b070eaa028df15607f49..4077ae94a0a06eb2cd1ffa008327399c67fee3dd 100644
 a/algebra/upred_tactics.v
+++ b/algebra/upred_tactics.v
@@ 1,5 +1,6 @@
From algebra Require Export upred.
From algebra Require Export upred_big_op.
+Import uPred.
Module upred_reflection. Section upred_reflection.
Context {M : cmraT}.
@@ 89,7 +90,7 @@ Module upred_reflection. Section upred_reflection.
Proof.
intros ??. rewrite !eval_flatten.
rewrite (flatten_cancel e1 e1' ns) // (flatten_cancel e2 e2' ns) //; csimpl.
 rewrite !fmap_app !big_sep_app. apply uPred.sep_mono_r.
+ rewrite !fmap_app !big_sep_app. apply sep_mono_r.
Qed.
Class Quote (Σ1 Σ2 : list (uPred M)) (P : uPred M) (e : expr) := {}.
@@ 144,3 +145,98 @@ Tactic Notation "ecancel" open_constr(Ps) :=
  @uPred_entails ?M _ _ =>
close Ps (@nil (uPred M)) ltac:(fun Qs => cancel Qs)
end.
+
+(* Some more generic uPred tactics.
+ TODO: Naming. *)
+
+Ltac revert_intros tac :=
+ lazymatch goal with
+   ∀ _, _ => let H := fresh in intro H; revert_intros tac; revert H
+   _ => tac
+ end.
+
+(** Assumes a goal of the shape P ⊑ ▷ Q. Alterantively, if Q
+ is built of ★, ∧, ∨ with ▷ in all branches; that will work, too.
+ Will turn this goal into P ⊑ Q and strip ▷ in P below ★, ∧, ∨. *)
+Ltac u_strip_later :=
+ let rec strip :=
+ lazymatch goal with
+   (_ ★ _) ⊑ ▷ _ =>
+ etrans; last (eapply equiv_entails_sym, later_sep);
+ apply sep_mono; strip
+   (_ ∧ _) ⊑ ▷ _ =>
+ etrans; last (eapply equiv_entails_sym, later_and);
+ apply sep_mono; strip
+   (_ ∨ _) ⊑ ▷ _ =>
+ etrans; last (eapply equiv_entails_sym, later_or);
+ apply sep_mono; strip
+   ▷ _ ⊑ ▷ _ => apply later_mono; reflexivity
+   _ ⊑ ▷ _ => apply later_intro; reflexivity
+ end
+ in let rec shape_Q :=
+ lazymatch goal with
+   _ ⊑ (_ ★ _) =>
+ (* Force the later on the LHS to be toplevel, matching laters
+ below ★ on the RHS *)
+ etrans; first (apply equiv_entails, later_sep; reflexivity);
+ (* Match the arm recursively *)
+ apply sep_mono; shape_Q
+   _ ⊑ (_ ∧ _) =>
+ etrans; first (apply equiv_entails, later_and; reflexivity);
+ apply sep_mono; shape_Q
+   _ ⊑ (_ ∨ _) =>
+ etrans; first (apply equiv_entails, later_or; reflexivity);
+ apply sep_mono; shape_Q
+   _ ⊑ ▷ _ => apply later_mono; reflexivity
+ (* We fail if we don't find laters in all branches. *)
+ end
+ in revert_intros ltac:(etrans; [shape_Q];
+ etrans; last eapply later_mono; first solve [ strip ]).
+
+(** Transforms a goal of the form ∀ ..., ?0... → ?1 ⊑ ?2
+ into True ⊑ ∀..., ■?0... → ?1 → ?2, applies tac, and
+ the moves all the assumptions back. *)
+Ltac u_revert_all :=
+ lazymatch goal with
+   ∀ _, _ => let H := fresh in intro H; u_revert_all;
+ (* TODO: Really, we should distinguish based on whether this is a
+ dependent function type or not. Right now, we distinguish based
+ on the sort of the argument, which is suboptimal. *)
+ first [ apply (const_intro_impl _ _ _ H); clear H
+  revert H; apply forall_elim']
+   ?C ⊑ _ => trans (True ∧ C)%I;
+ first (apply equiv_entails_sym, left_id, _; reflexivity);
+ apply impl_elim_l'
+ end.
+
+(** This starts on a goal of the form ∀ ..., ?0... → ?1 ⊑ ?2.
+ It applies löb where all the Coq assumptions have been turned into logical
+ assumptions, then moves all the Coq assumptions back out to the context,
+ applies [tac] on the goal (now of the form _ ⊑ _), and then reverts the
+ Coq assumption so that we end up with the same shape as where we started,
+ but with an additional assumption ★ed to the context *)
+Ltac u_löb tac :=
+ u_revert_all;
+ (* Add a box *)
+ etrans; last (eapply always_elim; reflexivity);
+ (* We now have a goal for the form True ⊑ P, with the "original" conclusion
+ being locked. *)
+ apply löb_strong; etransitivity;
+ first (apply equiv_entails, left_id, _; reflexivity);
+ apply: always_intro;
+ (* Now introduce again all the things that we reverted, and at the bottom,
+ do the work *)
+ let rec go :=
+ lazymatch goal with
+   _ ⊑ (∀ _, _) => apply forall_intro;
+ let H := fresh in intro H; go; revert H
+   _ ⊑ (■ _ → _) => apply impl_intro_l, const_elim_l;
+ let H := fresh in intro H; go; revert H
+ (* This is the "bottom" of the goal, where we see the impl introduced
+ by u_revert_all as well as the ▷ from löb_strong and the □ we added. *)
+   ▷ □ ?R ⊑ (?L → _) => apply impl_intro_l;
+ trans (L ★ ▷ □ R)%I;
+ first (eapply equiv_entails, always_and_sep_r, _; reflexivity);
+ tac
+ end
+ in go.
diff git a/heap_lang/wp_tactics.v b/heap_lang/wp_tactics.v
index d1495340c3695124b68e858a6382c00c8476438f..ef6461201ff25601e9c2ce098352aeb638bd1246 100644
 a/heap_lang/wp_tactics.v
+++ b/heap_lang/wp_tactics.v
@@ 1,100 +1,7 @@
+From algebra Require Export upred_tactics.
From heap_lang Require Export tactics substitution.
Import uPred.
(* TODO: The next few tactics are not wpspecific at all. They should move elsewhere. *)

Ltac revert_intros tac :=
 lazymatch goal with
   ∀ _, _ => let H := fresh in intro H; revert_intros tac; revert H
   _ => tac
 end.

(** Assumes a goal of the shape P ⊑ ▷ Q. Alterantively, if Q
 is built of ★, ∧, ∨ with ▷ in all branches; that will work, too.
 Will turn this goal into P ⊑ Q and strip ▷ in P below ★, ∧, ∨. *)
Ltac u_strip_later :=
 let rec strip :=
 lazymatch goal with
   (_ ★ _) ⊑ ▷ _ =>
 etrans; last (eapply equiv_entails_sym, later_sep);
 apply sep_mono; strip
   (_ ∧ _) ⊑ ▷ _ =>
 etrans; last (eapply equiv_entails_sym, later_and);
 apply sep_mono; strip
   (_ ∨ _) ⊑ ▷ _ =>
 etrans; last (eapply equiv_entails_sym, later_or);
 apply sep_mono; strip
   ▷ _ ⊑ ▷ _ => apply later_mono; reflexivity
   _ ⊑ ▷ _ => apply later_intro; reflexivity
 end
 in let rec shape_Q :=
 lazymatch goal with
   _ ⊑ (_ ★ _) =>
 (* Force the later on the LHS to be toplevel, matching laters
 below ★ on the RHS *)
 etrans; first (apply equiv_entails, later_sep; reflexivity);
 (* Match the arm recursively *)
 apply sep_mono; shape_Q
   _ ⊑ (_ ∧ _) =>
 etrans; first (apply equiv_entails, later_and; reflexivity);
 apply sep_mono; shape_Q
   _ ⊑ (_ ∨ _) =>
 etrans; first (apply equiv_entails, later_or; reflexivity);
 apply sep_mono; shape_Q
   _ ⊑ ▷ _ => apply later_mono; reflexivity
 (* We fail if we don't find laters in all branches. *)
 end
 in revert_intros ltac:(etrans; [shape_Q];
 etrans; last eapply later_mono; first solve [ strip ]).

(** Transforms a goal of the form ∀ ..., ?0... → ?1 ⊑ ?2
 into True ⊑ ∀..., ■?0... → ?1 → ?2, applies tac, and
 the moves all the assumptions back. *)
Ltac u_revert_all :=
 lazymatch goal with
   ∀ _, _ => let H := fresh in intro H; u_revert_all;
 (* TODO: Really, we should distinguish based on whether this is a
 dependent function type or not. Right now, we distinguish based
 on the sort of the argument, which is suboptimal. *)
 first [ apply (const_intro_impl _ _ _ H); clear H
  revert H; apply forall_elim']
   ?C ⊑ _ => trans (True ∧ C)%I;
 first (apply equiv_entails_sym, left_id, _; reflexivity);
 apply impl_elim_l'
 end.

(** This starts on a goal of the form ∀ ..., ?0... → ?1 ⊑ ?2.
 It applies löb where all the Coq assumptions have been turned into logical
 assumptions, then moves all the Coq assumptions back out to the context,
 applies [tac] on the goal (now of the form _ ⊑ _), and then reverts the
 Coq assumption so that we end up with the same shape as where we started,
 but with an additional assumption ★ed to the context *)
Ltac u_löb tac :=
 u_revert_all;
 (* Add a box *)
 etrans; last (eapply always_elim; reflexivity);
 (* We now have a goal for the form True ⊑ P, with the "original" conclusion
 being locked. *)
 apply löb_strong; etransitivity;
 first (apply equiv_entails, left_id, _; reflexivity);
 apply: always_intro;
 (* Now introduce again all the things that we reverted, and at the bottom,
 do the work *)
 let rec go :=
 lazymatch goal with
   _ ⊑ (∀ _, _) => apply forall_intro;
 let H := fresh in intro H; go; revert H
   _ ⊑ (■ _ → _) => apply impl_intro_l, const_elim_l;
 let H := fresh in intro H; go; revert H
 (* This is the "bottom" of the goal, where we see the impl introduced
 by u_revert_all as well as the ▷ from löb_strong and the □ we added. *)
   ▷ □ ?R ⊑ (?L → _) => apply impl_intro_l;
 trans (L ★ ▷ □ R)%I;
 first (eapply equiv_entails, always_and_sep_r, _; reflexivity);
 tac
 end
 in go.

(** wpspecific helper tactics *)
(* First try to productively strip off laters; if that fails, at least
cosmetically get rid of laters in the conclusion. *)