Module AbVarExpEq


Require Import ToString Coqlib Utf8 Maps ShareTree Sets Util.
Require Import AdomLib.
Require Import IdealExpr AbIdealEnv.
Require Import Integers ZIntervals.
Require Import Hash FastArith.
Require Import FactMsgHelpers.

Module MVar := T.

Module SVar <: SET with Definition elt := var := Tree2Set(MVar).
Module SVarO := SetOp(SVar).

Module HExp_Cells <: Hashable.
  Definition t := iexpr.
  Definition eq : Util.EqDec t := _.
  Definition hash : hashable t := _.
  Definition bits := F8.
End HExp_Cells.
Module MExp := Hashtable HExp_Cells.

Module SExp <: SET with Definition elt := iexpr := Tree2Set(MExp).
Module SExpO := SetOp(SExp).

Module MExp_Properties := Tree_Properties(MExp).

Fixpoint iexpr_free_vars_acc (e:iexpr) (acc:SVar.t) : SVar.t :=
  match e with
  | IEvar _ x => SVar.add x acc
  | IEconst _ | IEZitv _ => acc
  | IEunop _ e => iexpr_free_vars_acc e acc
  | IEbinop _ e1 e2 => iexpr_free_vars_acc e1 (iexpr_free_vars_acc e2 acc)
  end.

Lemma iexpr_free_vars_acc_iff:
  ∀ e acc x,
    SVar.mem x (iexpr_free_vars_acc e acc) <->
    SVar.mem x acc \/ SVar.mem x (iexpr_free_vars_acc e SVar.empty).
Proof.
  induction e; simpl; intros.
  - rewrite SVar.mem_add. setoid_rewrite SVar.mem_add. intuition.
    apply SVar.mem_empty in H. destruct H.
  - intuition. apply SVar.mem_empty in H0. destruct H0.
  - intuition. apply SVar.mem_empty in H0. destruct H0.
  - apply IHe.
  - rewrite IHe1, IHe2. rewrite (IHe1 (iexpr_free_vars_acc e2 SVar.empty)). intuition.
Qed.

Lemma eval_iexpr_upd_free_acc:
  ∀ x ρ n v e,
    eval_iexpr ρ e v ->
  ∀ acc,
    ~SVar.mem x (iexpr_free_vars_acc e acc) ->
    eval_iexpr (upd ρ x n) e v.
Proof.
  induction 1; intros; simpl in *.
  - rewrite SVar.mem_add in H1.
    constructor; auto. unfold upd.
    destruct @eq_dec; auto. destruct H1. auto.
  - constructor.
  - constructor. auto.
  - econstructor; eauto.
  - econstructor; eauto.
    eapply IHeval_iexpr2. contradict H2. rewrite iexpr_free_vars_acc_iff. eauto.
Qed.

Definition iexpr_free_vars (e:iexpr) : SVar.t :=
  iexpr_free_vars_acc e SVar.empty.

Lemma eval_iexpr_upd_free:
  ∀ x ρ n v e,
    ~SVar.mem x (iexpr_free_vars e) ->
    eval_iexpr ρ e v ->
    eval_iexpr (upd ρ x n) e v.
Proof.
  intros. eapply eval_iexpr_upd_free_acc; eauto.
Qed.

Fixpoint iexpr_is_free (e:iexpr) (y:var) : bool :=
  match e with
  | IEvar _ x => if eq_dec y x then true else false
  | IEconst _ | IEZitv _ => false
  | IEunop _ e => iexpr_is_free e y
  | IEbinop _ e1 e2 =>
    iexpr_is_free e1 y || iexpr_is_free e2 y
  end.

Lemma iexpr_is_free_iexpr_free_vars:
  ∀ e y,
    SVar.mem y (iexpr_free_vars e) ->
    iexpr_is_free e y.
Proof.
  induction e; simpl; intros; try discriminate; auto.
  - destruct @eq_dec. auto. unfold iexpr_free_vars in H. simpl in H.
    rewrite SVar.mem_add in H. destruct n, H; auto.
    edestruct SVar.mem_empty; eauto.
  - edestruct SVar.mem_empty; eauto.
  - edestruct SVar.mem_empty; eauto.
  - unfold is_true, iexpr_free_vars in *. rewrite Bool.orb_true_iff.
    simpl in H. apply iexpr_free_vars_acc_iff in H. destruct H; eauto.
Qed.

Fixpoint ite_iexpr_free_vars_acc (e:ite_iexpr) (acc:SVar.t) : SVar.t :=
  match e with
  | Leaf e => iexpr_free_vars_acc e acc
  | Ite c et ef =>
    iexpr_free_vars_acc c
      (ite_iexpr_free_vars_acc et
         (ite_iexpr_free_vars_acc ef acc))
  end.

Lemma ite_iexpr_free_vars_acc_iff:
  ∀ e acc x,
    SVar.mem x (ite_iexpr_free_vars_acc e acc) <->
    SVar.mem x acc \/ SVar.mem x (ite_iexpr_free_vars_acc e SVar.empty).
Proof.
  induction e; simpl; intros.
  - apply iexpr_free_vars_acc_iff.
  - rewrite iexpr_free_vars_acc_iff, IHe1, IHe2.
    rewrite (iexpr_free_vars_acc_iff _ (ite_iexpr_free_vars_acc e1 (ite_iexpr_free_vars_acc e2 SVar.empty))).
    rewrite (IHe1 (ite_iexpr_free_vars_acc e2 SVar.empty)).
    intuition.
Qed.

Lemma eval_ite_iexpr_upd_free_acc:
  ∀ x ρ n v e,
    eval_ite_iexpr ρ e v ->
  ∀ acc,
    ~SVar.mem x (ite_iexpr_free_vars_acc e acc) ->
    eval_ite_iexpr (upd ρ x n) e v.
Proof.
  induction 1; intros; simpl in *.
  - constructor. eapply eval_iexpr_upd_free_acc; eauto.
  - rewrite (iexpr_free_vars_acc_iff c) in H1.
    rewrite (ite_iexpr_free_vars_acc_iff et) in H1.
    rewrite (ite_iexpr_free_vars_acc_iff ef) in H1.
    econstructor; intuition eauto using eval_iexpr_upd_free_acc.
  - rewrite (iexpr_free_vars_acc_iff c) in H1.
    rewrite (ite_iexpr_free_vars_acc_iff et) in H1.
    rewrite (ite_iexpr_free_vars_acc_iff ef) in H1.
    econstructor 3; intuition eauto using eval_iexpr_upd_free_acc.
Qed.

Definition ite_iexpr_free_vars (e:ite_iexpr) : SVar.t :=
  ite_iexpr_free_vars_acc e SVar.empty.

Lemma eval_ite_iexpr_upd_free:
  ∀ x ρ n v e,
    ~SVar.mem x (ite_iexpr_free_vars e) ->
    eval_ite_iexpr ρ e v ->
    eval_ite_iexpr (upd ρ x n) e v.
Proof.
  intros. eapply eval_ite_iexpr_upd_free_acc; eauto.
Qed.

Definition max_eq_sz : Z := 30.
Definition max_fact_sz : Z := 10.
Opaque max_eq_sz.
Opaque max_fact_sz.

Fixpoint iexpr_subst (e:iexpr) (x:var) (e':iexpr) : iexpr.
refine (match e as ee return ee = e -> _ with
    | IEvar _ x' => fun _ => if eq_dec x x' then e' else e
    | IEconst _ | IEZitv _ => fun _ => e
    | IEunop op e1 => fun _ =>
      let e1' := iexpr_subst e1 x e' in
      ifeq e1' == e1 then e else IEunop op e1'
    | IEbinop op e1 e2 => fun _ =>
      let e1' := iexpr_subst e1 x e' in
      let e2' := iexpr_subst e2 x e' in
      ifeq e1' == e1 then
        ifeq e2' == e2 then e else IEbinop op e1' e2'
      else IEbinop op e1' e2'
  end eq_refl).
Proof.
  - abstract congruence.
  - intros; reflexivity.
Grab Existential Variables.
abstract (intros; destruct H0; subst; subst; subst op0; congruence).
Defined.

Lemma iexpr_subst_eval:
  ∀ ρ v e, eval_iexpr ρ e v ->
  ∀ x e', eval_iexpr ρ e' (ρ x) ->
          eval_iexpr ρ (iexpr_subst e x e') v.
Proof.
  induction 1; simpl; intros.
  - destruct @eq_dec. congruence. constructor; auto.
  - constructor; auto.
  - constructor; auto.
  - unfold physEq. econstructor; eauto.
  - unfold physEq. econstructor; eauto.
Qed.

Lemma iexpr_subst_free:
  ∀ e x e', ~SVar.mem x (iexpr_free_vars e') ->
  ∀ y, SVar.mem y (iexpr_free_vars (iexpr_subst e x e')) ->
       xy /\
       (SVar.mem y (iexpr_free_vars e) \/
        SVar.mem y (iexpr_free_vars e')).
Proof.
  induction e; simpl; intros.
  - destruct @eq_dec.
    + split. congruence. auto.
    + split. 2:now auto.
      unfold iexpr_free_vars in *; simpl in *.
      rewrite SVar.mem_add in H0. destruct H0. edestruct SVar.mem_empty; eauto. congruence.
  - edestruct SVar.mem_empty; eauto.
  - edestruct SVar.mem_empty; eauto.
  - auto.
  - unfold physEq, iexpr_free_vars in *. simpl in *.
    rewrite iexpr_free_vars_acc_iff in *.
    destruct H0.
    apply IHe2 in H0; auto. now intuition.
    apply IHe1 in H0; auto. now intuition.
Qed.

Fixpoint ite_iexpr_subst (e:ite_iexpr) (x:var) (e':iexpr) : ite_iexpr.
refine (match e as ee return ee = e -> _ with
    | Leaf e1 => fun _ =>
      let e1' := iexpr_subst e1 x e' in
      ifeq e1' == e1 then e else Leaf e1'
    | Ite c e1 e2 => fun _ =>
      let c' := iexpr_subst c x e' in
      let e1' := ite_iexpr_subst e1 x e' in
      let e2' := ite_iexpr_subst e2 x e' in
      ifeq c' == c then
        ifeq e1' == e1 then
          ifeq e2' == e2 then e else Ite c' e1' e2'
        else Ite c' e1' e2'
      else Ite c' e1' e2'
  end eq_refl).
Proof.
  - abstract congruence.
  - intros; reflexivity.
Grab Existential Variables.
  + intros; reflexivity.
  + abstract (intros; destruct H0, H1; subst; subst; congruence).
Defined.

Lemma ite_iexpr_subst_eval:
  ∀ ρ v e, eval_ite_iexpr ρ e v ->
  ∀ x e', eval_iexpr ρ e' (ρ x) ->
          eval_ite_iexpr ρ (ite_iexpr_subst e x e') v.
Proof.
  induction 1; simpl; intros; unfold physEq.
  - constructor. apply iexpr_subst_eval; auto.
  - constructor 2. apply iexpr_subst_eval; auto. eauto.
  - constructor 3. apply iexpr_subst_eval; auto. eauto.
Qed.

Lemma ite_iexpr_subst_free:
  ∀ e x e', ~SVar.mem x (iexpr_free_vars e') ->
  ∀ y, SVar.mem y (ite_iexpr_free_vars (ite_iexpr_subst e x e')) ->
       xy /\
       (SVar.mem y (ite_iexpr_free_vars e) \/
        SVar.mem y (iexpr_free_vars e')).
Proof.
  induction e; simpl; intros.
  - apply iexpr_subst_free; auto.
  - unfold physEq, ite_iexpr_free_vars in *. simpl in *.
    rewrite iexpr_free_vars_acc_iff, ite_iexpr_free_vars_acc_iff in *.
    destruct H0 as [[]|].
    apply IHe2 in H0; auto. now intuition.
    apply IHe1 in H0; auto. now intuition.
    apply iexpr_subst_free in H0; auto. now intuition.
Qed.

Record t0 :=
  { eqs : MVar.t ite_iexpr;
    eqs_free : MVar.t SVar.t;
    facts : MExp.t bool;
    facts_free : MVar.t SExp.t
  }.
Record t0_inv (ab:t0) : Prop :=
  { eqs_free_correct :
      ∀ x e, MVar.get x ab.(eqs) = Some e ->
      ∀ y, SVar.mem y (ite_iexpr_free_vars e) ->
      ∃ s, MVar.get y ab.(eqs_free) = Some s /\ SVar.mem x s;
    facts_free_correct :
      ∀ e b, MExp.get e ab.(facts) = Some b ->
      ∀ y, SVar.mem y (iexpr_free_vars e) ->
      ∃ s, MVar.get y ab.(facts_free) = Some s /\ SExp.mem e s }.

Definition t := {ab: t0 | t0_inv ab}.

Record gamm0 (ab:t0) (ρ:var -> ideal_num) : Prop :=
  { eqs_gamm :
      ∀ x e, MVar.get x ab.(eqs) = Some e -> eval_ite_iexpr ρ ex);
    facts_gamm :
      ∀ e b, MExp.get e ab.(facts) = Some b ->
             eval_iexpr ρ e (INz (if b then F1 else F0)) }.
Instance gamm0' : gamma_op t0 (var -> ideal_num) := gamm0.

Instance gamm : gamma_op t (var -> ideal_num) :=
  fun ab => gamm0 (proj1_sig ab).

Definition enrich_qchan (ab:t) (c:query_chan) : query_chan :=
  {| get_var_ty := c.(get_var_ty);
     get_itv := c.(get_itv);
     get_congr := c.(get_congr);
     get_eq_expr x := MVar.get x (proj1_sig ab).(eqs);
     linearize_expr := c.(linearize_expr) |}.

Lemma enrich_qchan_correct:
  ∀ ρ ab c, ρ ∈ γ ab -> ρ ∈ γ c -> ρ ∈ γ (enrich_qchan ab c).
Proof.
  intros ρ ab c H H0; constructor; simpl.
  - intros v. apply H0.
  - intros e a H1. apply H0; auto.
  - intros e a H1. apply H0; auto.
  - intros x e H1. apply H; auto.
  - intros. eapply H0; eauto.
Qed.

Definition remove_eq (x:var) (e:ite_iexpr) (ab:t0) : t0 :=
  {| eqs := MVar.remove x ab.(eqs);
     eqs_free :=
       SVar.fold (fun x' free =>
                    match MVar.get_set x' free with
                      | (None, _) => free
                      | (Some s, setter) => setter (SVar.rem x s)
                    end)
                 (ite_iexpr_free_vars e) ab.(eqs_free);
     facts := ab.(facts); facts_free := ab.(facts_free)
  |}.

Lemma remove_eq_inv:
  ∀ x e ab,
    t0_inv ab ->
    MVar.get x ab.(eqs) = Some e ->
    t0_inv (remove_eq x e ab).
Proof.
  unfold remove_eq. constructor; simpl. 2:apply H.
  intros. rewrite MVar.grspec in H1. destruct MVar.elt_eq. discriminate.
  rewrite SVar.fold_spec. induction (SVar.elements (ite_iexpr_free_vars e)).
  + eapply H; eauto.
  + simpl.
    match goal with
      | |- context [MVar.get_set a ?FR] =>
        destruct (MVar.get_set_spec a FR), (MVar.get_set a FR) as [[] ?]
    end. 2:now auto.
    simpl in *. rewrite H4, MVar.gsspec. destruct MVar.elt_eq; auto.
    eexists. split. eauto. subst a. destruct IHl as (s & Hs & Hs').
    fold SVar.t in *. assert (s = t1) by congruence. subst.
    rewrite SVar.mem_rem. auto.
Qed.

Lemma remove_eq_get:
  ∀ x e ab y, MVar.get y (remove_eq x e ab).(eqs) =
     if eq_dec y x then None else MVar.get y ab.(eqs).
Proof.
  intros. apply MVar.grspec.
Qed.

Lemma remove_eq_gamm0:
  ∀ x e ab ρ, ρ ∈ γ ab -> ρ ∈ γ (remove_eq x e ab).
Proof.
  constructor. 2:apply H.
  simpl. intros. rewrite MVar.grspec in H0. destruct (MVar.elt_eq x0 x). discriminate. apply H; auto.
Qed.

Definition unfree_eq (x:var) (ab:t0) (x':var) (e':iexpr) : t0 :=
  match MVar.get_set x ab.(eqs) with
  | (Some e, setter) =>
    let newe := ite_iexpr_subst e x' e' in
    if Z.leb (ite_iexpr_sz newe) max_eq_sz then
      let free :=
          match MVar.get_set x' ab.(eqs_free) with
          | (None, _) => ab.(eqs_free)
          | (Some s, setter) => setter (SVar.rem x s)
          end
      in
      let free :=
          SVar.fold
            (fun y free =>
               let '(o, setter) := MVar.get_set y free in
               setter
                 (SVar.add x match o with None => SVar.empty | Some s => s end))
            (iexpr_free_vars e') free
      in
      {| eqs := setter newe; eqs_free := free;
         facts := ab.(facts); facts_free := ab.(facts_free)
      |}
    else remove_eq x e ab
  | (None, _) => ab
  end.

Lemma unfree_eq_inv:
  ∀ ab x x' e',
    ~SVar.mem x' (iexpr_free_vars e') ->
    t0_inv ab ->
    t0_inv (unfree_eq x ab x' e').
Proof.
  unfold unfree_eq. intros.
  destruct (MVar.get_set_spec x ab.(eqs)), MVar.get_set as [[]?]; simpl in *. 2:now auto.
  destruct Z.leb. 2:apply remove_eq_inv; now auto.
  rewrite H2. constructor; simpl. 2:apply H0.
  intros. rewrite SVar.fold_spec.
  rewrite MVar.gsspec in H3. destruct MVar.elt_eq.
  - inv H3. apply ite_iexpr_subst_free in H4. 2:now auto. destruct H4.
    revert H4. rewrite (SVar.elements_spec (iexpr_free_vars e')).
    induction (SVar.elements (iexpr_free_vars e')); intros; simpl.
    + destruct (MVar.get_set_spec x' (ab.(eqs_free))), MVar.get_set as [[]?].
      rewrite H6, MVar.gso by auto. eapply H0. eauto. now intuition.
      eapply H0. eauto. now intuition.
    + match goal with
        | |- context [MVar.get_set a ?FR] =>
          destruct (MVar.get_set_spec a FR), (MVar.get_set a FR)
      end; rewrite H6, MVar.gsspec; destruct MVar.elt_eq.
      eexists. split. eauto. rewrite SVar.mem_add. auto.
      apply IHl. simpl in H4. now intuition.
  - induction (SVar.elements (iexpr_free_vars e')); simpl.
    + destruct (MVar.get_set_spec x' (ab.(eqs_free))), MVar.get_set as [[]?].
      2:eapply H0; now eauto.
      rewrite H6, MVar.gsspec. destruct MVar.elt_eq.
      eexists. split. eauto. apply SVar.mem_rem. split. 2:auto.
      eapply H0 in H3; eauto. simpl in H5. destruct H3 as (s & Hs & Hsx0). congruence.
      eapply H0. eauto. auto.
    + match goal with
        | |- context [MVar.get_set a ?FR] =>
          destruct (MVar.get_set_spec a FR), (MVar.get_set a FR)
      end; rewrite H6. rewrite MVar.gsspec. destruct MVar.elt_eq. 2:now auto.
      subst. eexists. split. eauto.
      destruct IHl as (s & Hs & Hsx0).
      assert (o = Some s) by (etransitivity; [apply H5|apply Hs]). subst o.
      rewrite SVar.mem_add. auto.
Qed.

Lemma unfree_eq_gamm0:
  ∀ x ab x' e' ρ,
    ρ ∈ γ ab ->
    eval_iexpr ρ e' (ρ x') ->
    ρ ∈ γ (unfree_eq x ab x' e').
Proof.
  intros. unfold unfree_eq.
  destruct (MVar.get_set_spec x ab.(eqs)), MVar.get_set as [[]?]. 2:now auto.
  destruct Z.leb. 2:apply remove_eq_gamm0; now auto.
  constructor. 2:now apply H.
  simpl. intros. rewrite H2, MVar.gsspec in H3. destruct MVar.elt_eq. 2:apply H; now auto.
  inv H3. apply ite_iexpr_subst_eval. eapply H; now auto. auto.
Qed.

Lemma unfree_eq_get:
  ∀ ab x x' e' y,
    match MVar.get y (unfree_eq x ab x' e').(eqs) with
    | None => MVar.get y ab.(eqs) = None \/ x = y
    | Some e =>
      if eq_dec x y then
        ∃ ee, MVar.get x ab.(eqs) = Some ee /\
              e = ite_iexpr_subst ee x' e'
      else MVar.get y ab.(eqs) = Some e
    end.
Proof.
  intros. unfold unfree_eq.
  destruct (MVar.get_set_spec x ab.(eqs)), MVar.get_set as [[]?].
  - destruct Z.leb.
    + simpl. rewrite H0, MVar.gsspec.
      destruct (MVar.elt_eq y x), (eq_dec x y); try congruence. eauto.
      destruct (MVar.get y ab.(eqs)); auto.
    + rewrite remove_eq_get. destruct (eq_dec y x). auto.
      destruct (eq_dec x y). congruence.
      destruct (MVar.get y ab.(eqs)); auto.
  - destruct (MVar.get y ab.(eqs)) eqn:EQ. 2:now auto.
    destruct MVar.elt_eq. simpl in *; congruence. auto.
Qed.

Definition forget0_eqs (x:var) (e:option iexpr) (ab:t0) : t0 :=
  match MVar.get x ab.(eqs_free) with
  | None => ab
  | Some s =>
    let ab := SVar.fold
       (fun x' ab =>
          match e with
          | Some e => unfree_eq x' ab x e
          | _ =>
            match MVar.get x' ab.(eqs) with
            | Some e' => remove_eq x' e' ab
            | None => ab
            end
          end) s ab
    in
    {| eqs := ab.(eqs); eqs_free := MVar.remove x ab.(eqs_free);
       facts := ab.(facts); facts_free := ab.(facts_free) |}
  end.

Lemma forget0_eqs_inv:
  ∀ ab x e,
    t0_inv ab ->
    match e with
    | Some e => ~SVar.mem x (iexpr_free_vars e)
    | _ => True
    end ->
    t0_inv (forget0_eqs x e ab).
Proof.
  intros. unfold forget0_eqs. destruct (MVar.get x (eqs_free ab)) eqn:EQ. 2:now auto.
  match goal with
    | |- context [SVar.fold ?f t1 ab] => assert (t0_inv (SVar.fold f t1 ab))
  end.
  { rewrite SVar.fold_spec.
    induction (SVar.elements t1). now auto. simpl. destruct e.
    apply unfree_eq_inv, IHl. auto.
    match goal with
      | |- context [MVar.get a ?m] => destruct (MVar.get a m) eqn:EQ'
    end.
    apply remove_eq_inv; now auto. now auto. }
  constructor. 2:now apply H1. simpl.
  intros. rewrite MVar.grspec. destruct MVar.elt_eq. 2:eapply H1; now eauto. exfalso. subst. clear H1.
  assert (∀ e1, MVar.get x0 ab.(eqs) = Some e1 ->
                SVar.mem x (ite_iexpr_free_vars e1) ->
                In x0 (SVar.elements t1)).
  { intros. apply SVar.elements_spec. eapply H in H1. 2:eauto.
    destruct H1 as (? & ? & ?). congruence. }
  rewrite SVar.fold_spec in H2. generalize dependent e0.
  induction (SVar.elements t1). now auto. simpl. destruct e; intros e0 He0 Hxe0.
  - pose proof unfree_eq_get (fold_right (λ (x':SVar.elt) ab0, unfree_eq x' ab0 x i) ab l) a x i x0.
    rewrite He0 in H2. destruct @eq_dec.
    + subst. destruct H2 as (ee & Hee & ->). apply ite_iexpr_subst_free in Hxe0; now intuition.
    + eapply IHl; eauto. intros. edestruct H1; eauto. congruence.
  - match goal with
      | H:context [MVar.get a ?m] |- _ => destruct (MVar.get a m) eqn:EQ'
    end.
    + rewrite remove_eq_get in He0. destruct @eq_dec. congruence.
      eapply IHl; eauto. intros. edestruct H1; eauto. congruence.
    + eapply IHl; eauto. intros. edestruct H1; eauto. congruence.
Qed.

Lemma forget0_eqs_gamm0:
  ∀ x e ab ρ,
    ρ ∈ γ ab ->
    match e with
    | Some e => eval_iexpr ρ ex)
    | None => True
    end ->
    ρ ∈ γ (forget0_eqs x e ab).
Proof.
  intros. unfold forget0_eqs. destruct MVar.get. 2:now auto.
  match goal with
    | |- context [SVar.fold ?f t1 ab] => cut (ρ ∈ γ (SVar.fold f t1 ab))
  end.
  { intro HH. constructor; apply HH. }
  rewrite SVar.fold_spec. induction SVar.elements. now auto. simpl. destruct e.
  apply unfree_eq_gamm0; auto.
  destruct MVar.get. apply remove_eq_gamm0. auto. auto.
Qed.

Definition remove_fact (e:iexpr) (ab:t0) : t0 :=
  {| facts := MExp.remove e ab.(facts);
     facts_free :=
       SVar.fold (fun x' free =>
                    match MVar.get_set x' free with
                    | (None, _) => free
                    | (Some s, setter) => setter (MExp.remove e s)
                    end)
              (iexpr_free_vars e) ab.(facts_free);
         eqs := ab.(eqs); eqs_free := ab.(eqs_free)
  |}.

Lemma remove_fact_inv:
  ∀ ab p, t0_inv ab -> t0_inv (remove_fact p ab).
Proof.
  unfold remove_fact. constructor; simpl; intros.
  - eapply H; eauto.
  - rewrite MExp.grspec in H0. destruct (eq_dec e p). discriminate.
    rewrite SVar.fold_spec. induction (SVar.elements (iexpr_free_vars p)).
    + destruct H. eauto.
    + simpl.
      match goal with
        | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m) as [[] ?]
      end. 2:now auto.
      simpl in *. rewrite H3, MVar.gsspec. destruct MVar.elt_eq; auto.
      eexists. split. eauto.
      subst a. destruct IHl as (s & Hs & Hs').
      assert (Some s = Some t1) by (rewrite <- Hs, H2; auto).
      inv H4. rewrite SExp.mem_rem. auto.
Qed.

Lemma remove_fact_gamm0:
  ∀ ab ρ e, ρ ∈ γ ab -> ρ ∈ γ (remove_fact e ab).
Proof.
  unfold remove_fact. constructor; intros.
  - eapply H; auto.
  - eapply H. simpl in H0. rewrite MExp.grspec in H0. destruct (eq_dec e0 e); now intuition.
Qed.

Lemma remove_fact_get:
  ∀ e ab ee, MExp.get ee (remove_fact e ab).(facts) =
     if eq_dec ee e then None else MExp.get ee ab.(facts).
Proof.
  intros. apply MExp.grspec.
Qed.

Definition try_add_fact_aux (e:iexpr) (b:bool) (ab:t0) : t0+⊥ :=
  if iexpr_det e && Z.leb (iexpr_sz e) max_fact_sz then
    match MExp.get_set e ab.(facts) with
      | (None, setter) =>
        NotBot
          {| eqs := ab.(eqs); eqs_free := ab.(eqs_free);
             facts := setter b;
             facts_free :=
               SVar.fold
                 (fun x' free =>
                    let '(o, setter) := MVar.get_set x' free in
                    setter
                      (SExp.add e
                        (match o with
                         | None => SExp.empty
                         | Some s => s
                         end)))
                 (iexpr_free_vars e) ab.(facts_free)
          |}
      | (Some b', _) =>
        if Bool.eqb b b' then NotBot ab else Bot
    end
  else NotBot ab.

Lemma try_add_fact_aux_inv:
  ∀ e f ab ab',
    t0_inv ab ->
    try_add_fact_aux e f ab = NotBot ab' ->
    t0_inv ab'.
Proof.
  unfold try_add_fact_aux. intros.
  destruct (iexpr_det e && (iexpr_sz e <=? max_fact_sz)) eqn:EQ. 2:inv H0; auto.
  rewrite Bool.andb_true_iff in EQ. destruct EQ as [? _].
  destruct (MExp.get_set_spec _ e (facts ab)) as [_ ?].
  destruct (MExp.get_set e ab.(facts)) as [[]?].
  destruct (eqb f b); inv H0. auto.
  inv H0. constructor.
  - intros. eapply H; eauto.
  - simpl. rewrite H2.
    simpl. intros. rewrite MExp.gsspec in H0. destruct @eq_dec.
    + inv H0. revert H3. rewrite SVar.fold_spec, SVar.elements_spec.
      induction (SVar.elements (iexpr_free_vars e)).
      * easy.
      * intros. simpl.
        match goal with
          | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m)
        end.
        rewrite H4, MVar.gsspec. destruct MVar.elt_eq.
        subst. eexists. split. eauto. rewrite SExp.mem_add. auto.
        destruct H0. now intuition. auto.
    + rewrite SVar.fold_spec. induction (SVar.elements (iexpr_free_vars e)).
      * eapply H; eauto.
      * simpl.
        match goal with
          | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m)
        end.
        rewrite H5, MVar.gsspec. destruct MVar.elt_eq; auto. eexists. split. eauto.
        subst. apply SExp.mem_add. destruct IHl as [? []].
        unfold SExp.t in *. simpl in H4. rewrite H4, H6. auto.
Qed.

Lemma try_add_fact_aux_gamm0:
  ∀ e (b:bool) ρ ab,
    ρ ∈ γ ab ->
    eval_iexpr ρ e (INz (if b then F1 else F0)) ->
    ρ ∈ γ (try_add_fact_aux e b ab).
Proof.
  intros. unfold try_add_fact_aux.
  destruct (iexpr_det e && (iexpr_sz e <=? max_fact_sz)) eqn:EQ. 2:auto.
  rewrite Bool.andb_true_iff in EQ. destruct EQ as [? _].
  destruct (MExp.get_set_spec _ e (facts ab)).
  destruct (MExp.get_set e ab.(facts)) as [[]?].
  destruct (eqb b b0) eqn:EQ. auto.
  symmetry in H2. apply H in H2. eapply iexpr_det_ok in H0; eauto. destruct b, b0; discriminate.
  constructor.
  - simpl. eapply H.
  - simpl. rewrite H3. intros.
    rewrite MExp.gsspec in H4. destruct @eq_dec.
    + inv H4. auto.
    + eapply H. eauto.
Qed.

Definition unfree_fact (e:iexpr) (ab:t0) (x':var) (e':iexpr) : t0+⊥ :=
  match MExp.get e ab.(facts) with
  | Some b =>
    let newe := iexpr_subst e x' e' in
    try_add_fact_aux newe b (remove_fact e ab)
  | None => NotBot (remove_fact e ab)
  end.

Lemma unfree_fact_inv:
  ∀ ab e x' e' ab',
    t0_inv ab ->
    unfree_fact e ab x' e' = NotBot ab' ->
    t0_inv ab'.
Proof.
  unfold unfree_fact. intros. destruct MExp.get.
  eapply try_add_fact_aux_inv, H0. apply remove_fact_inv. auto.
  inv H0. apply remove_fact_inv. auto.
Qed.

Lemma unfree_fact_gamm0:
  ∀ e ab x' e' ρ,
    ρ ∈ γ ab ->
    eval_iexpr ρ e' (ρ x') ->
    ρ ∈ γ (unfree_fact e ab x' e').
Proof.
  intros. unfold unfree_fact. destruct (MExp.get e ab.(facts)) eqn:EQ.
  - apply try_add_fact_aux_gamm0.
    apply remove_fact_gamm0. auto.
    apply iexpr_subst_eval, H0. apply H. auto.
  - apply remove_fact_gamm0. auto.
Qed.

Lemma unfree_fact_get:
  ∀ ab e x' e' ab' ee,
    unfree_fact e ab x' e' = NotBot ab' ->
    match MExp.get ee ab'.(facts) with
    | None => MExp.get ee ab.(facts) = None \/ e = ee
    | Some b =>
      ee = iexpr_subst e x' e' /\ MExp.get e ab.(facts) = Some b \/
      MExp.get ee ab.(facts) = Some b /\ (eee \/ ~SVar.mem x' (iexpr_free_vars e))
    end.
Proof.
  intros. unfold unfree_fact in H.
  destruct (MExp.get e (facts ab)) eqn:EQ.
  - unfold try_add_fact_aux in H.
    destruct (iexpr_det (iexpr_subst e x' e') &&
                        (iexpr_sz (iexpr_subst e x' e') <=? max_fact_sz)).
    + destruct (MExp.get_set_spec _ (iexpr_subst e x' e') (facts (remove_fact e ab))), MExp.get_set as [[]?].
      * rewrite remove_fact_get in H0. simpl in H0. destruct @eq_dec. discriminate.
        destruct (eqb b b0) eqn:EQbb0; inv H.
        rewrite remove_fact_get. destruct MExp.elt_eq. now auto.
        destruct (MExp.get ee (facts ab)); now auto.
      * inv H. simpl. rewrite H1, MExp.gsspec.
        destruct MExp.elt_eq. auto.
        rewrite remove_fact_get. destruct MExp.elt_eq. now auto.
        destruct (MExp.get ee (facts ab)); now auto.
    + inv H. rewrite remove_fact_get.
      destruct MExp.elt_eq. auto.
      destruct (MExp.get ee); auto.
  - inv H. rewrite remove_fact_get.
    destruct MExp.elt_eq. auto.
    destruct (MExp.get ee); auto.
Qed.

Definition forget0_facts (x:var) (e:option iexpr) (ab:t0) : t0+⊥ :=
  match MVar.get x ab.(facts_free) with
  | None => NotBot ab
  | Some s =>
    do ab <-
      SExp.fold (fun f ab =>
                   do ab <- ab;
                   match e with
                   | Some e => unfree_fact f ab x e
                   | None => ret (remove_fact f ab)
                   end)
        s (NotBot ab);
    ret {| eqs := ab.(eqs); eqs_free := ab.(eqs_free);
           facts := ab.(facts); facts_free := MVar.remove x ab.(facts_free) |}
  end.

Lemma forget0_facts_inv:
  ∀ ab x e ab',
    t0_inv ab ->
    match e with
    | Some e => ~SVar.mem x (iexpr_free_vars e)
    | _ => True
    end ->
    forget0_facts x e ab = NotBot ab' ->
    t0_inv ab'.
Proof.
  intros. unfold forget0_facts in H1. destruct (MVar.get x (facts_free ab)) eqn:EQ. 2:congruence.
  match goal with
  | H0:context [SExp.fold ?f t1 (NotBot ab)] |- _ =>
    assert (∀ ab', SExp.fold f t1 (NotBot ab) = NotBot ab' -> t0_inv ab');
      [|destruct (SExp.fold f t1 (NotBot ab)) eqn:EQ'; inv H1]
  end.
  { rewrite SExp.fold_spec.
    induction (SExp.elements t1); simpl in *. congruence.
    destruct e.
    - destruct (fold_right (λ (f : SExp.elt) ab0, do ab1 <- ab0; unfree_fact f ab1 x i)).
      discriminate.
      simpl. intros. eapply unfree_fact_inv, H2. eauto.
    - destruct (fold_right (λ (f : SExp.elt) ab0, do ab1 <- ab0; NotBot (remove_fact f ab1))).
      discriminate.
      intros. inv H2. apply remove_fact_inv. auto. }
  specialize (H2 _ eq_refl).
  constructor. intros. eapply H2; eauto.
  simpl. intros. rewrite MVar.grspec. destruct (MVar.elt_eq y x). 2:eapply H2; now eauto. exfalso. subst. clear H2.
  destruct (MExp.get e0 ab.(facts)) eqn:EQ''.
  - assert (In e0 (SExp.elements t1)).
    { apply SExp.elements_spec. eapply H in EQ''. 2:now eauto.
      destruct EQ'' as (? & ? & ?). congruence. }
    rewrite SExp.fold_spec in EQ'.
    generalize dependent x0. induction (SExp.elements t1). now auto. simpl in *. destruct e.
    + destruct (fold_right (λ (f : SExp.elt) ab0, do ab1 <- ab0; unfree_fact f ab1 x i)). discriminate.
      intros. pose proof unfree_fact_get _ _ _ _ _ e0 EQ'. rewrite H1 in H4.
      destruct H4 as [[]|[? []]].
      * subst e0. apply iexpr_subst_free in H3; now intuition.
      * destruct H2. congruence. eauto.
      * destruct H2. congruence. eauto.
    + destruct (fold_right (λ (f : SExp.elt) ab0, do ab1 <- ab0; NotBot (remove_fact f ab1))). discriminate.
      intros. inv EQ'. rewrite remove_fact_get in H1. destruct @eq_dec. discriminate.
      destruct H2. congruence. eauto.
  - generalize dependent x0. rewrite SExp.fold_spec.
    induction (SExp.elements t1). intros. inv EQ'. congruence. simpl in *.
    match type of IHl with
      | ∀ x0, ?X = _ -> _ -> False => destruct X
    end. discriminate. specialize (IHl _ eq_refl). destruct e.
    + simpl. intros. apply unfree_fact_get with (ee:=e0) in EQ'.
      rewrite H1 in EQ'. destruct EQ'.
      * destruct H2. subst e0. apply iexpr_subst_free in H3; now intuition.
      * now intuition.
    + intros. inv EQ'. rewrite remove_fact_get in H1.
      destruct @eq_dec. discriminate. auto.
Qed.

Lemma forget0_facts_gamm0:
  ∀ x e ab ρ,
    ρ ∈ γ ab ->
    match e with
    | Some e => eval_iexpr ρ ex)
    | None => True
    end ->
    ρ ∈ γ (forget0_facts x e ab).
Proof.
  intros. unfold forget0_facts. destruct MVar.get. 2:now auto.
  eapply botbind_spec.
  { intros. constructor; apply H1. }
  rewrite SExp.fold_spec. induction SExp.elements. now auto.
  eapply botbind_spec, IHl.
  destruct e.
  intros. apply unfree_fact_gamm0; auto.
  intros. apply remove_fact_gamm0; auto.
Qed.

Definition forget0 (x:var) (ab:t0) : t0+⊥ :=
  let e := MVar.get x ab.(eqs) in
  let e' :=
    match e with
    | Some (Leaf e) => if iexpr_is_free e x then None else Some e
    | _ => None
    end
  in
  let ab := match e with None => ab | Some e => remove_eq x e ab end in
  forget0_facts x e' (forget0_eqs x e' ab).

Lemma forget0_inv:
  ∀ ab x ab', t0_inv ab ->
              forget0 x ab = NotBot ab' ->
              t0_inv ab'.
Proof.
  intros.
  eapply forget0_facts_inv, H0. apply forget0_eqs_inv.
  destruct (MVar.get x ab.(eqs)) eqn:EQ. apply remove_eq_inv; now auto. now auto.
  destruct MVar.get as [[]|]; auto.
  pose proof (iexpr_is_free_iexpr_free_vars i x).
  destruct iexpr_is_free. auto. intro. specialize (H1 H2). inv H1.
  destruct (MVar.get x ab.(eqs)) as [[]|] eqn:EQ; auto.
  pose proof (iexpr_is_free_iexpr_free_vars i x).
  destruct iexpr_is_free. auto. intro. specialize (H1 H2). inv H1.
Qed.

Lemma forget0_gamm0:
  ∀ x ab ρ,
    ρ ∈ γ ab ->
    ρ ∈ γ (forget0 x ab).
Proof.
  unfold forget0. intros.
  apply forget0_facts_gamm0. apply forget0_eqs_gamm0.
  destruct MVar.get. apply remove_eq_gamm0. auto. auto.
  destruct (MVar.get x ab.(eqs)) as [[]|] eqn:EQ; auto.
  destruct iexpr_is_free. auto. eapply H in EQ. inv EQ. auto.
  destruct (MVar.get x ab.(eqs)) as [[]|] eqn:EQ; auto.
  destruct iexpr_is_free. auto. eapply H in EQ. inv EQ. auto.
Qed.

Lemma eqs_free_forget0_facts:
  ∀ x e ab ab',
    forget0_facts x e ab = NotBot ab' ->
    ab'.(eqs_free) = ab.(eqs_free).
Proof.
  intros. unfold forget0_facts in H. destruct MVar.get. 2:congruence.
  match type of H with
    | context [SExp.fold ?f t1 (NotBot ab)] => destruct (SExp.fold f t1 (NotBot ab)) eqn:EQ
  end. discriminate. inv H.
  rewrite SExp.fold_spec in EQ. generalize dependent x0.
  induction SExp.elements; simpl in *; intros. congruence.
  match type of IHl with
    | context [fold_right ?f (NotBot ab) l] => destruct (fold_right f (NotBot ab) l)
  end. inv EQ. specialize (IHl _ eq_refl). simpl in *.
  destruct e.
  - unfold unfree_fact in EQ.
    destruct MExp.get.
    + unfold try_add_fact_aux in EQ.
      destruct andb. destruct @MExp.get_set as [[]?].
      destruct eqb; inv EQ. apply IHl. inv EQ; apply IHl. inv EQ; apply IHl.
    + inv EQ; apply IHl.
  - inv EQ; apply IHl.
Qed.

Lemma eqs_forget0_facts:
  ∀ x e ab ab',
    forget0_facts x e ab = NotBot ab' ->
    ab'.(eqs) = ab.(eqs).
Proof.
  intros. unfold forget0_facts in H. destruct MVar.get. 2:congruence.
  match type of H with
  | context [SExp.fold ?f t1 (NotBot ab)] => destruct (SExp.fold f t1 (NotBot ab)) eqn:EQ
  end. discriminate. inv H.
  rewrite SExp.fold_spec in EQ. generalize dependent x0.
  induction SExp.elements; simpl in *; intros. congruence.
  match type of IHl with
  | context [fold_right ?f (NotBot ab) l] => destruct (fold_right f (NotBot ab) l)
  end. inv EQ. specialize (IHl _ eq_refl). simpl in *.
  destruct e.
  - unfold unfree_fact in EQ.
    destruct MExp.get.
    + unfold try_add_fact_aux in EQ.
      destruct andb. destruct @MExp.get_set as [[]?].
      destruct eqb; inv EQ. apply IHl. inv EQ; apply IHl. inv EQ; apply IHl.
    + inv EQ; apply IHl.
  - inv EQ; apply IHl.
Qed.

Lemma eqs_forget0_eqs:
  ∀ x e ab x' e',
    MVar.get x' (forget0_eqs x e ab).(eqs) = Some e' ->
    ∃ e', MVar.get x' ab.(eqs) = Some e'.
Proof.
  intros. unfold forget0_eqs in H. destruct (MVar.get x). 2:now eauto.
  simpl in H. rewrite SVar.fold_spec in H.
  generalize dependent e'. induction SVar.elements. now eauto. simpl. intros. destruct e.
  - pose proof unfree_eq_get (fold_right (λ (x':SVar.elt) ab0, unfree_eq x' ab0 x i) ab l) a x i x'.
    rewrite H in H0. destruct @eq_dec. 2:now eauto.
    subst. destruct H0 as (ee & Hee & Hxee). eauto.
  - destruct (MVar.get a).
    rewrite remove_eq_get in H. destruct @eq_dec. discriminate. eauto. eauto.
Qed.

Lemma forget0_gamm0_upd:
  ∀ x ρ n ab,
    t0_inv ab ->
    ρ ∈ γ ab ->
    (upd ρ x n) ∈ γ (forget0 x ab).
Proof.
  intros. pose proof (forget0_gamm0 x _ _ H0).
  destruct (forget0 x ab) eqn:EQ. auto. constructor.
  - intros. apply eval_ite_iexpr_upd_free.
    + intro. eapply eqs_free_correct in H3. 3:eauto. 2:eapply forget0_inv; now eauto.
      destruct H3 as (s & Hs & Hx0s).
      erewrite eqs_free_forget0_facts in Hs by apply EQ.
      unfold forget0_eqs in Hs.
      match type of Hs with
      | context [match MVar.get x ?m with _ => _ end] =>
        destruct (MVar.get x m) eqn:EQ'
      end.
      simpl in Hs. rewrite MVar.grs in Hs. discriminate. congruence.
    + unfold upd. destruct @eq_dec. 2:apply H1; auto. subst. exfalso.
      erewrite eqs_forget0_facts in H2 by apply EQ. apply eqs_forget0_eqs in H2.
      destruct H2 as (e' & He'). destruct (MVar.get x ab.(eqs)) eqn:EQ'. 2:congruence.
      rewrite remove_eq_get in He'. destruct @eq_dec; congruence.
  - intros. apply eval_iexpr_upd_free, H1, H2.
    intro. eapply facts_free_correct in H3. 3:eauto. 2:eapply forget0_inv; now eauto.
    destruct H3 as (s & Hs & Hes). unfold forget0, forget0_facts in EQ.
    match type of EQ with
    | context [match MVar.get x ?m with _ => _ end] =>
      destruct (MVar.get x m) eqn:EQ'
    end. 2:congruence.
    match type of EQ with
    | context [do ab0 <- ?X; _] => destruct X
    end; inv EQ.
    simpl in Hs. rewrite MVar.grs in Hs. discriminate.
Qed.

Definition forget (x:var) (ab:t * query_chan) (chan:query_chan): (t * messages_chan)+⊥.
refine
  (match forget0 x (proj1_sig (fst ab)) as ab return _ = ab -> _ with
   | NotBot ab => fun EQ => NotBot (exist _ ab _, nil)
   | Bot => fun _ => Bot
   end eq_refl).
Proof.
  clear - EQ. abstract (eapply forget0_inv; [|eauto]; instantiate; apply proj2_sig).
Defined.

Lemma forget_correct :
  ∀ x ρ n ab chan, ρ ∈ γ ab -> (upd ρ x n) ∈ γ chan ->
                   (upd ρ x n) ∈ γ (forget x ab chan).
Proof.
  intros. unfold forget. generalize (forget_subproof x ab).
  pose proof forget0_gamm0_upd x ρ n (proj1_sig (fst ab)) (proj2_sig (fst ab)) (proj1 H).
  destruct (forget0 x (proj1_sig (fst ab))). auto.
  split. auto. constructor.
Qed.

Definition try_add_eq (x:var) (e:iexpr) (ab:t0) : t0 :=
  if Z.leb (iexpr_sz e) max_eq_sz then
    match MVar.get_set x ab.(eqs) with
    | (None, setter) =>
      {| eqs := setter (Leaf e);
         eqs_free :=
           SVar.fold
             (fun x' free =>
                let '(o, setter') := MVar.get_set x' free in
                setter' (SVar.add x (match o with
                                     | None => SVar.empty
                                     | Some s => s
                                     end)))
             (iexpr_free_vars e) ab.(eqs_free);
         facts := ab.(facts); facts_free := ab.(facts_free) |}
    | (Some _, _) => ab
    end
  else ab.

Lemma try_add_eq_inv:
  ∀ ab x e, t0_inv ab -> t0_inv (try_add_eq x e ab).
Proof.
  intros. unfold try_add_eq.
  destruct Z.leb. 2:auto.
  destruct (MVar.get_set_spec x (eqs ab)). destruct MVar.get_set as [[] ?]. auto.
  constructor; simpl.
  - intros. rewrite H1, MVar.gsspec in H2. destruct MVar.elt_eq.
    + inv H2. change (is_true (SVar.mem y (iexpr_free_vars e))) in H3.
      revert H3. rewrite SVar.fold_spec, SVar.elements_spec.
      induction (SVar.elements (iexpr_free_vars e)).
      * easy.
      * intros. simpl.
        match goal with
          | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m)
        end.
        rewrite H4, MVar.gsspec. destruct MVar.elt_eq; auto.
        subst. eexists. split. eauto. apply SVar.mem_add. auto.
        destruct H2. now intuition. auto.
    + rewrite SVar.fold_spec. induction (SVar.elements (iexpr_free_vars e)).
      * eapply H; eauto.
      * simpl.
        match goal with
          | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m)
        end.
        rewrite H5, MVar.gsspec. destruct MVar.elt_eq; auto. eexists. split. eauto.
        subst. destruct IHl as [? []]. apply SVar.mem_add.
        fold SVar.t in *. simpl in H4. rewrite H4, H6. auto.
  - apply H.
Qed.

Lemma try_add_eq_gamm0:
  ∀ x e ρ ab,
    ρ ∈ γ ab -> eval_iexpr ρ ex) -> t0_inv ab ->
    ρ ∈ γ (try_add_eq x e ab).
Proof.
  intros.
  assert (
      ρ ∈ γ {| eqs := MVar.set x (Leaf e) ab.(eqs);
                   eqs_free := SVar.fold
                                 (fun x' free =>
                                    let '(o, setter') := MVar.get_set x' free in
                                    setter'
                                       (SVar.add x (match o with
                                          | None => SVar.empty
                                          | Some s => s
                                        end)))
                               (iexpr_free_vars e) ab.(eqs_free);
                   facts := ab.(facts); facts_free := ab.(facts_free)
            |}).
  { constructor.
    - simpl. intros.
      rewrite MVar.gsspec in H2. destruct MVar.elt_eq.
      + inv H2. constructor. auto.
      + apply H. auto.
    - simpl. eapply H. }
  unfold try_add_eq. destruct Z.leb. 2:auto.
  destruct (MVar.get_set_spec x (eqs ab)). destruct MVar.get_set as [[] ?]. auto.
  rewrite H4. auto.
Qed.

Opaque try_add_eq.

Definition assign (x:var) (e:iexpr) (ab:t * query_chan) (chan:query_chan):
  (t * messages_chan)+⊥.
refine
  (if
    match e with
    | IEvar _ x' => if eq_dec x x' then true else false
    | _ => false
    end
  then NotBot (fst ab, nil)
  else
    let e :=
      if iexpr_is_free e x then
        match MVar.get x (proj1_sig (fst ab)).(eqs) with
        | Some (Leaf e') =>
          if iexpr_is_free e' x then None else Some (iexpr_subst e x e')
        | _ => None
        end
      else
        Some e
    in
    let e := do e <- e; if iexpr_det e then Some e else None in
    match e with
    | None => forget x ab chan
    | Some e =>
      match forget0 x (proj1_sig (fst ab)) as ab return ab = _ -> (t * messages_chan)+⊥ with
      | Bot => fun _ => Bot
      | NotBot ab => fun EQ =>
        let ab := try_add_eq x e ab in
        NotBot (exist _ ab _, nil)
      end eq_refl
    end).
Proof.
  clear -EQ.
  abstract (eapply try_add_eq_inv, forget0_inv, eq_sym, EQ; instantiate; apply proj2_sig).
Defined.

Lemma assign_correct:
  ∀ x e ρ n ab chan,
    ρ ∈ γ ab -> (upd ρ x n) ∈ γ chan ->
    neval_iexpr ρ e ->
    (upd ρ x n) ∈ γ (assign x e ab chan).
Proof.
  intros. unfold assign.
  match goal with
  | |- γ (if ?B then _ else _) _ => destruct B eqn:EQb
  end.
  - assert (∀ y, ρ y = ρ +[ x => n] y).
    { intros. unfold upd. destruct @eq_dec. 2:auto. subst.
      destruct e; try discriminate.
      destruct (eq_dec x v); try discriminate. inv H1. auto. }
    assert (∀ e n ρ, eval_iexpr ρ e n ->
                     ∀ ρ', (∀ y:var, ρ y = ρ' y) -> eval_iexpr ρ' e n).
    { induction 1; econstructor; eauto. congruence. }
    assert (∀ e n ρ, eval_ite_iexpr ρ e n ->
                     ∀ ρ', (∀ y:var, ρ y = ρ' y) -> eval_ite_iexpr ρ' e n).
    { induction 1; intros.
      - constructor. eauto.
      - constructor 2; eauto.
      - constructor 3; eauto. }
    split. 2:constructor.
    constructor; intros.
    + eapply H4, H2. rewrite <- H2. apply (proj1 H). auto.
    + eapply H3, H2. eapply (proj1 H). eauto.
  - match goal with
      | |- γ match ?E with _ => _ end _ => destruct E eqn:?
    end.
    2:apply forget_correct; now auto.
    pose proof forget0_gamm0_upd x ρ n (proj1_sig (fst ab)) (proj2_sig (fst ab)) (proj1 H).
    pose proof fun ab' => forget0_inv (proj1_sig (fst ab)) x ab' (proj2_sig (fst ab)).
    generalize (assign_subproof x ab i).
    destruct (forget0 x (proj1_sig (fst ab))). now auto.
    split. 2:now constructor.
    apply try_add_eq_gamm0, H3, eq_refl. apply H2.
    eapply eval_iexpr_upd_free_acc.
    + unfold upd. destruct @eq_dec. 2:congruence.
      destruct (iexpr_is_free e x). 2:simpl in Heqo; destruct iexpr_det; congruence.
      destruct (MVar.get x (proj1_sig (fst ab)).(eqs)) as [[]|] eqn:EQget;
        try (simpl in Heqo; congruence).
      destruct (iexpr_is_free i0 x); inv Heqo.
      destruct (iexpr_det (iexpr_subst e x i0)); inv H5.
      apply iexpr_subst_eval. auto. eapply H in EQget. inv EQget. now auto.
    + intro. pose proof H4. apply iexpr_is_free_iexpr_free_vars in H4.
      destruct (iexpr_is_free e x) eqn:FREEe. 2:simpl in Heqo; destruct iexpr_det; congruence.
      destruct (MVar.get x (proj1_sig (fst ab)).(eqs)) as [[]|]; try (simpl in Heqo; congruence).
      destruct (iexpr_is_free i0 x) eqn:FREEi; inv Heqo.
      destruct (iexpr_det (iexpr_subst e x i0)); inv H7.
      apply iexpr_subst_free in H5. now intuition.
      intro. apply iexpr_is_free_iexpr_free_vars in H6. congruence.
Qed.

Definition try_add_fact (e:iexpr) (b:bool) (ab:t) : t+⊥.
refine
  (let H := _ in
   match e with
   | IEconst _ => NotBot ab
   | _ =>
     match try_add_fact_aux e b (proj1_sig ab) as ab' return _ = ab' -> _ with
     | NotBot ab => fun EQ => NotBot (exist _ ab (H ab EQ))
     | Bot => fun _ => Bot
     end eq_refl
   end).
Proof.
  abstract (intros; eapply try_add_fact_aux_inv; eauto; instantiate; apply proj2_sig).
Defined.

Lemma try_add_fact_gamm:
  ∀ e (b:bool) ρ ab,
    ρ ∈ γ ab -> eval_iexpr ρ e (INz (if b then F1 else F0)) ->
    ρ ∈ γ (try_add_fact e b ab).
Proof.
  intros. unfold try_add_fact.
  generalize (try_add_fact_subproof e b ab).
  assert (G := try_add_fact_aux_gamm0 e b ρ (proj1_sig ab) H H0).
  destruct e; auto; destruct try_add_fact_aux; auto.
Qed.

Definition assume0 (e:iexpr) (b:bool) (ab:t0) : t0 :=
  match e with
  | IEbinop (IOcmp Ceq) (IEvar _ x) e'
  | IEbinop (IOcmp Ceq) e' (IEvar _ x) =>
    if b then try_add_eq x e' ab
    else ab
  | IEbinop (IOcmp Cne) (IEvar _ x) e'
  | IEbinop (IOcmp Cne) e' (IEvar _ x) =>
    if b then ab
    else try_add_eq x e' ab
  | _ => ab
  end.

Lemma assume0_inv:
  ∀ e b ab, t0_inv ab -> t0_inv (assume0 e b ab).
Proof.
  destruct e; simpl; intros; auto.
  assert (∀ x e, t0_inv (if b then try_add_eq x e ab else ab)).
  { destruct b; auto. intros. apply try_add_eq_inv. auto. }
  assert (∀ x e, t0_inv (if b then ab else try_add_eq x e ab)).
  { destruct b; auto. intros. apply try_add_eq_inv. auto. }
  assert (t0_inv (if b then ab else ab)). { destruct b; auto. }
  destruct i; auto; destruct c; auto;
  destruct e1, e2; auto;
  try (destruct i as [|[] []]; auto);
  try (destruct i0 as [|[] []]; auto).
Qed.

Lemma assume0_gamm0:
  ∀ c ρ ab (b:bool) v, t0_inv ab -> ρ ∈ γ ab -> v <> 0 ->
    (INz (if b return Zfast then v else F0)) ∈ eval_iexpr ρ c ->
    ρ ∈ γ (assume0 c b ab).
Proof.
  destruct c; simpl; eauto; intros.
  inv H2. inv H9; auto; auto.
  assert (∀ x e ρ ab, gamm0 ab ρ → eval_iexpr ρ ex) → t0_inv abgamm0 (try_add_eq x e ab) ρ).
  { intros. eapply try_add_eq_gamm0 in H3; eauto. }
  assert (0 ∈ γ (ZITV 0 0)) by (compute; split; discriminate).
  destruct c; simpl in H7;
    destruct b, (Z.compare_spec i1 i2); fastunwrap; try congruence; simpl in H7; inv H7;
    inv H6; inv H8; auto;
    (try (destruct itv as [|[] []]));
    (try (destruct itv0 as [|[] []])); auto;
    try eapply H2;
    try match goal with H : ρ ?id = _ |- context [ρ ?id] => rewrite H end;
    try match goal with H : ?x ∈ γ (ZITV 0 0) |- _ =>
        change (0 <= x <= 0) in H; assert (x = 0) by omega; subst; clear H end;
    (simpl; eauto; try (econstructor; eauto); omega).
Qed.

Definition assume (e:iexpr) (b:bool) (ab:t * query_chan): t+⊥.
refine (
  let '(e, b') := simplify_bool_expr e (snd ab) return t+⊥ in
  do ab <- try_add_fact e (xorb b b') (fst ab);
  NotBot (exist _ (assume0 e (xorb b b') (proj1_sig ab)) _)).
Proof.
abstract (eapply assume0_inv, proj2_sig). Defined.

Lemma assume_correct :
  ∀ e ρ ab (b:bool), ρ ∈ γ ab ->
    (INz (if b then F1 else F0)) ∈ eval_iexpr ρ e ->
    ρ ∈ γ (assume e b ab).
Proof.
  intros. unfold assume.
  pose proof (simplify_bool_expr_correct _ _ _ H0 _ (proj2 H)). destruct (simplify_bool_expr e (snd ab)).
  specialize (H1 _ _ eq_refl).
  eapply botbind_spec, try_add_fact_gamm.
  intros. apply assume0_gamm0 with (v:=1); try discriminate. apply proj2_sig. apply H2.
  destruct b, b0; auto. apply H. destruct b, b0; auto.
Qed.

Lemma ite_iexpr_eqdec : ∀ (e1 e2:ite_iexpr), {e1 = e2} + {e1 <> e2}.
Proof.
  decide equality. apply MExp.elt_eq. apply MExp.elt_eq.
Defined.

Definition leb (ab1:t * query_chan) (ab2:t) : bool.
refine (
  let ab1 := proj1_sig (fst ab1) in
  let ab2 := proj1_sig ab2 in
  MVar.shforall2 (fun _ e1' e2' =>
                    match e1', e2' with
                    | _, None => true
                    | None, _ => false
                    | Some e1, Some e2 =>
                      if ite_iexpr_eqdec e1 e2 then true else false
                    end)
              _ ab1.(eqs) ab2.(eqs) &&
  MExp.shforall2 (fun _ f1 f2 =>
                    match f1, f2 with
                    | _, None => true
                    | None, _ => false
                    | Some b1, Some b2 => Bool.eqb b1 b2
                    end)
                       _ ab1.(facts) ab2.(facts)).
Proof.
  - abstract (destruct v; auto; destruct ite_iexpr_eqdec; congruence).
  - abstract (destruct v as [[]|]; auto).
Defined.

Lemma leb_correct:
  ∀ ab1 ab2 ρ, leb ab1 ab2 = true -> ρ ∈ γ ab1 -> ρ ∈ γ ab2.
Proof.
  unfold leb. destruct ab1 as [[[ab1 ?] ?] ?], ab2 as [[ab2 ?] ?]; simpl.
  intros. destruct H0.
  rewrite Bool.andb_true_iff in H. destruct H. constructor; intros; simpl in *.
  - apply H0. simpl.
    rewrite MVar.shforall2_correct in H. specialize (H x).
    destruct MVar.get, MVar.get; try congruence.
    destruct ite_iexpr_eqdec; congruence.
  - apply H0. simpl.
    rewrite MExp.shforall2_correct in H2. specialize (H2 e).
    destruct MExp.get as [[]|], MExp.get as [[]|]; try discriminate; auto.
Qed.

Definition top (chan:query_chan) : (t * messages_chan)+⊥.
refine (
    NotBot (exist _
                  {| eqs := MVar.empty _; eqs_free := MVar.empty _;
                     facts := MExp.empty _; facts_free := MVar.empty _
                  |} _, nil)).
Proof.
  constructor.
  - abstract (intros; rewrite MVar.gempty in H; discriminate).
  - abstract (intros; rewrite MExp.gempty in H; discriminate).
Defined.

Lemma top_correct:
  ∀ chan ρ, ρ ∈ γ chan -> ρ ∈ γ (top chan).
Proof.
  intros. split. 2:constructor. constructor; simpl; intros.
  - rewrite MVar.gempty in H0. discriminate.
  - rewrite MExp.gempty in H0. discriminate.
Qed.

Definition join0_cond (widen:bool) (ab1:t0) (ab2:t0) : option (iexpr * bool).
refine (
  if widen then None
  else
    let candidates :=
      MExp.shcombine_diff (fun e ob1 ob2 =>
        match ob1, ob2 with
        | None, _ | _, None => None
        | Some b1, Some b2 =>
          if xorb b1 b2 then ob1 else None
        end)
        _ ab1.(facts) ab2.(facts)
    in
    MExp.fold (fun acc e b =>
      match acc with
      | Some eb => Some eb
      | None => Some (e, b)
      end) candidates None).
Proof.
abstract (destruct v as [[]|]; auto). Defined.

Lemma join0_cond_correct :
  ∀ w ab1 ab2 ρ e b,
    join0_cond w ab1 ab2 = Some (e, b) ->
    (ρ ∈ γ ab1 -> eval_iexpr ρ e (INz (if b then F1 else F0))) /\
    (ρ ∈ γ ab2 -> eval_iexpr ρ e (INz (if negb b then F1 else F0))).
Proof.
  unfold join0_cond. intros w ab1 ab2 ρ. destruct w. discriminate.
  apply MExp_Properties.fold_rec. eauto. discriminate.
  intros. rewrite MExp.gshcombine_diff in H0.
  destruct a; inv H2. auto.
  destruct (MExp.get e (facts ab1)) eqn:?; [|discriminate].
  destruct (MExp.get e (facts ab2)) eqn:?; [|discriminate].
  assert (b1 = negb b0) by (destruct b0, b1; try reflexivity; try discriminate).
  subst b1. rewrite <- negb_xorb_r, xorb_nilpotent in H0. inv H0.
  split; intro GAM; eapply GAM; eauto.
Qed.

Definition join0_eqs (w:bool) (ab1:t0) (ab2:t0) (ab:t0) : t0.
refine(
  match join0_cond w ab1 ab2 with
  | Some (cond, swap) =>
    let diffs :=
      MVar.shcombine_diff (fun _ e1 e2 =>
        match e1, e2 with
        | Some e1, None => Some (e1, false)
        | Some e1, Some e2 =>
          if ite_iexpr_eqdec e1 e2 then None
          else
            if Z.leb (ite_iexpr_sz (Ite cond e1 e2)) max_eq_sz then Some (e2, true)
            else None
        | _, _ => None
        end)
        _ ab1.(eqs) ab2.(eqs)
    in
    let cond_free := iexpr_free_vars cond in
    let free :=
      MVar.fold (fun free x eb =>
        match eb with
        | (e, false) =>
          SVar.fold (fun x' free =>
            match MVar.get_set x' free with
            | (None, _) => free
            | (Some s, setter) => setter (SVar.rem x s)
            end)
            (ite_iexpr_free_vars e) free
        | (e, true) =>
          SVar.fold (fun x' free =>
            let '(o, setter) := MVar.get_set x' free in
            setter
              (SVar.add x
                (match o with
                 | None => SVar.empty
                 | Some s => s
                 end)))
            (ite_iexpr_free_vars_acc e cond_free) free
        end) diffs ab1.(eqs_free)
    in
    {| eqs :=
         MVar.shcombine (fun _ e1 e2 =>
           match e1, e2 with
           | None, _ | _, None => None
           | Some e1, Some e2 =>
             if ite_iexpr_eqdec e1 e2 then Some e1
             else
               let e' :=
                 if swap then Ite cond e1 e2
                 else Ite cond e2 e1
               in
               if Z.leb (ite_iexpr_sz e') max_eq_sz then Some e'
               else None
           end) _ ab1.(eqs) ab2.(eqs);
       eqs_free := free;
       facts := ab.(facts);
       facts_free := ab.(facts_free)
    |}
  | None =>
    let diffs :=
      MVar.shcombine_diff (fun _ e1 e2 =>
        match e1, e2 with
        | Some e1, None => Some e1
        | Some e1, Some e2 =>
          if ite_iexpr_eqdec e1 e2 then None
          else Some e1
        | _, _ => None
        end)
        _ ab1.(eqs) ab2.(eqs)
    in
    let free :=
      MVar.fold (fun free x e1 =>
        SVar.fold (fun x' free =>
          match MVar.get_set x' free with
          | (None, _) => free
          | (Some s, setter) => setter (SVar.rem x s)
          end)
          (ite_iexpr_free_vars e1) free)
        diffs ab1.(eqs_free)
    in
    {| eqs :=
         MVar.shcombine (fun _ e1' e2' =>
           match e1', e2' with
           | None, _ | _, None => None
           | Some e1, Some e2 =>
             if ite_iexpr_eqdec e1 e2 then e1'
             else None
           end) _ ab1.(eqs) ab2.(eqs);
       eqs_free := free;
       facts := ab.(facts);
       facts_free := ab.(facts_free)
    |}
  end);
  abstract (destruct v; auto; destruct ite_iexpr_eqdec; congruence).
Proof.
Defined.

Lemma join0_eqs_correct :
  ∀ w ab1 ab2 ab,
    t0_inv ab1 -> t0_inv ab2 -> t0_inv ab ->
    t0_inv (join0_eqs w ab1 ab2 ab) /\
    ∀ ρ, ρ ∈ (γ ab1 ∪ γ ab2) ->
         ρ ∈ γ ab ->
         ρ ∈ γ (join0_eqs w ab1 ab2 ab).
Proof.
  intros. pose proof (join0_cond_correct w ab1 ab2).
  unfold join0_eqs. destruct join0_cond as [[cond swap]|].
  - split.
    + split; try apply H1. simpl. intros.
      rewrite MVar.gshcombine in H3.
      destruct (MVar.get x (eqs ab1)) eqn:EQ1. 2:discriminate.
      destruct (MVar.get x (eqs ab2)) eqn:EQ2. 2:discriminate.
      rewrite MVar.fold_spec, <- fold_left_rev_right.
      match goal with
        | |- context [MVar.elements ?t] =>
          pose proof MVar.elements_correct (m:=t) (i:=x);
          pose proof (MVar.elements_complete t x)
      end.
      rewrite MVar.gshcombine_diff, EQ1, EQ2 in H5, H6.
      setoid_rewrite in_rev in H5. setoid_rewrite in_rev in H6.
      apply or_introl with (B := SVar.mem y (ite_iexpr_free_vars i)) in H5.
      revert H5 H6.
      replace (ite_iexpr_sz (if swap then Ite cond i i0 else Ite cond i0 i))
      with (iexpr_sz cond + ite_iexpr_sz i + ite_iexpr_sz i0 + 1)
        in H3 by (destruct swap; simpl; ring).
      induction rev.
      { simpl. intros. eapply H. eauto.
        destruct ite_iexpr_eqdec. congruence. destruct H5. edestruct H5. 2:auto.
        destruct Z.leb; inv H3. eauto. }
      { destruct a as (x' & e2 & []); simpl; intros.
        - rewrite SVar.fold_spec.
          pose proof proj1 (SVar.elements_spec
               (ite_iexpr_free_vars_acc e2 (iexpr_free_vars cond)) y).
          match type of IHl with
          | ?P -> _ => eapply or_introl with (B:=P) in H7
          end.
          revert H7. induction SVar.elements; intros.
          + apply IHl; auto. destruct H5, H7; auto.
            destruct ite_iexpr_eqdec. right. congruence.
            destruct Z.leb; inv H3.
            edestruct H5. eauto. 2:left; intros; inv H8; now auto.
            inv H3. right.
            rewrite ite_iexpr_free_vars_acc_iff in H7. simpl in H7.
            destruct swap; inv H4; unfold ite_iexpr_free_vars in H8; simpl in H8.
            apply iexpr_free_vars_acc_iff in H8.
            rewrite (ite_iexpr_free_vars_acc_iff i) in H8.
            now intuition.
            apply iexpr_free_vars_acc_iff in H8.
            rewrite (ite_iexpr_free_vars_acc_iff i0) in H8.
            now intuition.
          + fold SVar.t in *. simpl.
            match goal with
            | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m)
            end. rewrite H9.
            rewrite MVar.gsspec.
            destruct (MVar.elt_eq y a).
            2:destruct IHl0 as (s & -> & Hsx); [simpl in H7; now intuition|eauto].
            subst. eexists. split. eauto. rewrite SVar.mem_add. simpl in H8. subst o.
            destruct H5. 2:destruct IHl0 as (s & -> & Hsx); now auto.
            destruct ite_iexpr_eqdec. destruct IHl0 as (s & -> & Hsx); auto. right. left. discriminate.
            destruct Z.leb; inv H3.
            edestruct H5. eauto. inv H3. now auto.
            destruct IHl0 as (s & -> & Hsx); auto; right; left.
            intros. congruence.
        - rewrite SVar.fold_spec. induction SVar.elements.
          + apply IHl. 2:now auto. destruct H5; auto. left.
            destruct ite_iexpr_eqdec. discriminate.
            intros. edestruct H5. eauto. destruct Z.leb; congruence. auto.
          + simpl. destruct IHl0 as (s & Hs & Hsx).
            match goal with
            | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m) as [[] ?]
            end. 2:now eauto.
            rewrite H8, MVar.gsspec. destruct MVar.elt_eq. 2:now eauto.
            subst. eexists. split. eauto. apply SVar.mem_rem.
            fold SVar.t in *. simpl in H7. assert (s = t1) by congruence. subst. split. auto.
            intro. subst. specialize (H6 _ (or_introl eq_refl)).
            destruct ite_iexpr_eqdec, Z.leb; congruence. }
    + constructor; simpl. 2:apply H4.
      intros. rewrite MVar.gshcombine in H5.
      destruct (MVar.get x ab1.(eqs)) eqn:EQ1. 2:discriminate.
      destruct (MVar.get x ab2.(eqs)) eqn:EQ2. 2:discriminate.
      destruct ite_iexpr_eqdec.
      inv H5. subst. destruct H3; apply H3; auto.
      specialize (H2 ρ _ _ eq_refl).
      destruct H2. destruct swap, H3, Z.leb; inv H5.
      constructor 3. eauto. apply H3; auto.
      constructor 2. eauto. apply H3; auto.
      constructor 2. eauto. apply H3; auto.
      constructor 3. eauto. apply H3; auto.
  - split.
    + split; try apply H1. simpl. intros.
      rewrite MVar.gshcombine in H3.
      destruct (MVar.get x (eqs ab1)) eqn:EQ1. 2:discriminate.
      destruct (MVar.get x (eqs ab2)) eqn:EQ2. 2:discriminate.
      rewrite MVar.fold_spec, <- fold_left_rev_right.
      match goal with
      | |- context [MVar.elements ?t] => pose proof (MVar.elements_complete t x)
      end.
      rewrite MVar.gshcombine_diff, EQ1, EQ2 in H5.
      setoid_rewrite in_rev in H5.
      revert H5.
      induction rev.
      { simpl. intros. eapply H. eauto. destruct ite_iexpr_eqdec; congruence. }
      { destruct a as [x' e1]; simpl; intros.
        rewrite SVar.fold_spec. induction SVar.elements. auto.
        simpl. destruct IHl0 as (s & Hs & Hsx).
        match goal with
        | |- context [MVar.get_set a ?m] => destruct (MVar.get_set_spec a m), (MVar.get_set a m) as [[] ?]
        end. 2:now eauto.
        rewrite H7, MVar.gsspec. destruct MVar.elt_eq. 2:now eauto.
        subst. eexists. split. eauto. apply SVar.mem_rem.
        fold SVar.t in *. simpl in H6. assert (s = t1) by congruence. subst. split. auto.
        intro. subst. specialize (H5 _ (or_introl eq_refl)).
        destruct ite_iexpr_eqdec; congruence. }
    + constructor; simpl. 2:apply H4.
      intros. rewrite MVar.gshcombine in H5.
      destruct (MVar.get x ab1.(eqs)) eqn:EQ1. 2:discriminate.
      destruct (MVar.get x ab2.(eqs)) eqn:EQ2. 2:discriminate.
      destruct ite_iexpr_eqdec; inv H5. subst. destruct H3; apply H3; auto.
Qed.

Definition join0_facts (ab1:t0) (ab2:t0) (ab:t0) : t0.
refine (
  let diffs :=
    MExp.shcombine_diff (fun _ ob1 ob2 =>
      match ob1, ob2 with
      | Some _, None => Some tt
      | Some b1, Some b2 => if xorb b1 b2 then Some tt else None
      | _, _ => None
      end)
      _ ab1.(facts) ab2.(facts)
  in
  let free :=
    SExp.fold (fun e free =>
      SVar.fold (fun x' free =>
        match MVar.get_set x' free with
        | (None, _) => free
        | (Some s, setter) => setter (SExp.rem e s)
        end)
        (iexpr_free_vars e) free)
      diffs ab1.(facts_free)
  in
  {| facts :=
       MExp.shcombine (fun _ ob1 ob2 =>
         match ob1, ob2 with
         | None, _ | _, None => None
         | Some b1, Some b2 => if xorb b1 b2 then None else ob1
         end) _ ab1.(facts) ab2.(facts);
     facts_free := free;
     eqs := ab.(eqs);
     eqs_free := ab.(eqs_free)
  |});
  abstract (destruct v as [[]|]; auto; simpl; congruence).
Proof.
Defined.

Lemma join0_facts_correct :
  ∀ ab1 ab2 ab,
    t0_inv ab1 -> t0_inv ab2 -> t0_inv ab ->
    t0_inv (join0_facts ab1 ab2 ab) /\
    ∀ ρ, ρ ∈ (γ ab1 ∪ γ ab2) ->
         ρ ∈ γ ab ->
         ρ ∈ γ (join0_facts ab1 ab2 ab).
Proof.
  intros.
  unfold join0_facts. split.
  - split. apply H1.
    + simpl. intros.
      rewrite MExp.gshcombine in H2.
      destruct (MExp.get e (facts ab1)) eqn:EQ1. 2:discriminate.
      destruct (MExp.get e (facts ab2)) eqn:EQ2. 2:discriminate.
      rewrite SExp.fold_spec.
      match goal with
        | |- context [SExp.elements ?t] => pose proof (proj2 (SExp.elements_spec t e))
      end.
      unfold SExp.mem in H4. rewrite MExp.gshcombine_diff, EQ1, EQ2 in H4.
      revert H4. induction SExp.elements; simpl; intros.
      * eapply H. eauto. destruct b0, b1; simpl in H2; congruence.
      * rewrite SVar.fold_spec. induction SVar.elements; intros. auto.
        simpl. destruct IHl0 as (s & Hs & Hsx).
        match goal with
          | |- context [MVar.get_set a0 ?m] => destruct (MVar.get_set_spec a0 m), (MVar.get_set a0 m) as [[] ?]
        end. 2:now eauto.
        rewrite H6, MVar.gsspec. destruct MVar.elt_eq. 2:now eauto.
        subst. eexists. split. eauto. apply SExp.mem_rem.
        fold SExp.t in *. simpl in H5. assert (s = t1) by congruence. subst. split. auto.
        intro. subst. specialize (H4 (or_introl eq_refl)).
        destruct b0, b1; simpl in H2, H4; congruence.
  - constructor; simpl. apply H3.
    intros. rewrite MExp.gshcombine in H4.
    destruct (MExp.get e (facts ab1)) eqn:EQ1. 2:discriminate.
    destruct (MExp.get e (facts ab2)) eqn:EQ2. 2:discriminate.
    destruct b0, b1; inv H4; subst.
    destruct H2. apply H2 in EQ1; auto. apply H2 in EQ2; auto.
    destruct H2. apply H2 in EQ1; auto. apply H2 in EQ2; auto.
Qed.

Definition join0_aux (w:bool) (ab1:t0) (ab2:t0) : t0 :=
  let ab :=
    {| eqs := MVar.empty _; eqs_free := MVar.empty _;
       facts := MExp.empty _; facts_free := MVar.empty _ |}
  in
  let ab := join0_eqs w ab1 ab2 ab in
  let ab := join0_facts ab1 ab2 ab in
  {| eqs := ab.(eqs); eqs_free := ab.(eqs_free);
     facts := ab.(facts); facts_free := ab.(facts_free) |}.

Lemma join0_aux_correct :
  ∀ ab1 ab2 w,
    t0_inv ab1 -> t0_inv ab2 ->
    t0_inv (join0_aux w ab1 ab2) /\
    ∀ ρ, ρ ∈ (γ ab1 ∪ γ ab2) ->
         ρ ∈ γ (join0_aux w ab1 ab2).
Proof.
  intros.
  destruct (join0_eqs_correct w ab1 ab2
              {| eqs := MVar.empty _; eqs_free := MVar.empty _;
                 facts := MExp.empty _; facts_free := MVar.empty _ |}); auto.
  { constructor; simpl; intros.
    rewrite MVar.gempty in H1; discriminate.
    rewrite MExp.gempty in H1; discriminate. }
  edestruct (join0_facts_correct _ _ _ H H0 H1).
  split; constructor; intros.
  - eapply H3; eauto.
  - eapply H3; eauto.
  - eapply H4; eauto. eapply H2; eauto.
    constructor; simpl; intros.
    rewrite MVar.gempty in H7; discriminate.
    rewrite MExp.gempty in H7; discriminate.
  - eapply H4; eauto. eapply H2; eauto.
    constructor; simpl; intros.
    rewrite MVar.gempty in H7; discriminate.
    rewrite MExp.gempty in H7; discriminate.
Qed.

Program Instance join0 : join_op t t := join0_aux false.
Next Obligation.
apply join0_aux_correct; apply proj2_sig. Qed.
Program Definition widen0 : t -> t -> t := join0_aux true.
Next Obligation.
apply join0_aux_correct; apply proj2_sig. Qed.

Definition join (ab1:t * query_chan) (ab2:t * query_chan) (chan:query_chan) :
  (t * messages_chan)+⊥ :=
  NotBot ((fst ab1) ⊔ (fst ab2), nil).

Lemma join_correct :
  ∀ ab1 ab2 chan ρ,
    ρ ∈ γ ab1 \/ ρ ∈ γ ab2 -> ρ ∈ γ chan ->
    ρ ∈ γ (join ab1 ab2 chan).
Proof.
  intros. split. 2:constructor.
  apply join0_aux_correct; try apply proj2_sig. destruct H; [left|right]; apply H.
Qed.

Definition widen (ab1:t+⊥) (ab2:(t * query_chan)+⊥) (chan:query_chan+⊥) :
  t+⊥ * (t * messages_chan)+⊥ :=
  let w :=
      match ab1 with
      | Bot => do (ab2, _) <- ab2; ret ab2
      | NotBot ab1 =>
        match ab2 with
        | Bot => NotBot ab1
        | NotBot (ab2, _) => NotBot (widen0 ab1 ab2)
        end
      end
  in
  (w, do w <- w; ret (w, nil)).

Lemma widen_incr :
  ∀ ab1 ab2 chan ρ,
    ρ ∈ γ ab1 -> ρ ∈ γ chan ->
    ρ ∈ γ (widen ab1 ab2 chan).
Proof.
  intros. unfold widen. destruct ab1. contradiction. destruct ab2 as [|[]].
  split. auto. split. auto. constructor.
  assert (ρ ∈ γ (widen0 x t1)).
  { eapply join0_aux_correct. apply proj2_sig. apply proj2_sig. auto. }
  split. auto. split. auto. constructor.
Qed.

Program Instance abVarExpEq_env : ab_ideal_env t (t+⊥) :=
  {| id_leb := leb;
     id_top := top;
     id_join := join;
     id_init_iter := id;
     id_widen := widen;
     assign := assign;
     forget := forget;
     enrich_query_chan := enrich_qchan;
     process_msg m ab :=
       match m return _ with
       | Fact_msg e b =>
         do res <- assume_unfold assume e b ab;
         ret (res, m::nil)
       | _ => ret (fst ab, m::nil)
       end |}.
Next Obligation.
apply top_correct; auto. Qed.
Next Obligation.
eapply leb_correct; eauto. Qed.
Next Obligation.
eapply join_correct in H; eauto. Qed.
Next Obligation.
eapply widen_incr in H; eauto. Qed.
Next Obligation.
eapply assign_correct; eauto. Qed.
Next Obligation.
eapply forget_correct in H0; eauto. Qed.
Next Obligation.
  destruct m; try (split; [apply H|constructor; [auto|constructor]]).
  apply botbind_spec with ρ. split. auto.
  constructor. auto. constructor.
  apply assume_unfold_correct; auto.
  - intros. apply join0_aux_correct; eauto; apply proj2_sig.
  - intros. apply assume_correct; eauto.
Qed.
Next Obligation.
apply enrich_qchan_correct; auto. Qed.

Instance abVarExpEq_env_tostring
  {_:ToString var} {_:ToString (MVar.t ite_iexpr)} : ToString t :=
  fun x =>
    (MExp.fold (fun acc e b =>
                  acc ++ "[" ++
                  to_string e ++ "↦" ++
                  (if b:bool then "true" else "false") ++ "]")
               (proj1_sig x).(facts)
               "{" ++ "}"
               ++
    to_string (proj1_sig x).(eqs))%string.