(** * PE: Partial Evaluation *)

(* $Date: 2012-03-30 20:39:34 -0400 (Fri, 30 Mar 2012) $ *)
(* Chapter author/maintainer: Chung-chieh Shan *)

(** Equiv.v introduced constant folding as an example of a program
    transformation and proved that it preserves the meaning of the
    program.  Constant folding operates on manifest constants such
    as [ANum] expressions.  For example, it simplifies the command
    [Y ::= APlus (ANum 3) (ANum 1)] to the command [Y ::= ANum 4].
    However, it does not propagate known constants along data flow.
    For example, it does not simplify the sequence
        X ::= ANum 3; Y ::= APlus (AId X) (ANum 1)
    to
        X ::= ANum 3; Y ::= ANum 4
    because it forgets that [X] is [3] by the time it gets to [Y].

    We naturally want to enhance constant folding so that it
    propagates known constants and uses them to simplify programs.
    Doing so constitutes a rudimentary form of _partial evaluation_.
    As we will see, partial evaluation is so called because it is
    like running a program, except only part of the program can be
    evaluated because only part of the input to the program is known.
    For example, we can only simplify the program
        X ::= ANum 3; Y ::= AMinus (APlus (AId X) (ANum 1)) (AId Y)
    to
        X ::= ANum 3; Y ::= AMinus (ANum 4) (AId Y)
    without knowing the initial value of [Y]. *)

Require Export Imp.
Require Import FunctionalExtensionality.

(* ####################################################### *)
(** * Generalizing Constant Folding *)

(** The starting point of partial evaluation is to represent our
    partial knowledge about the state.  For example, between the two
    assignments above, the partial evaluator may know only that [X] is
    [3] and nothing about any other variable. *)

(** ** Partial States *)

(** Conceptually speaking, we can think of such partial states as the
    type [id -> option nat] (as opposed to the type [id -> nat] of
    concrete, full states).  However, in addition to looking up and
    updating the values of individual variables in a partial state, we
    may also want to compare two partial states to see if and where
    they differ, to handle conditional control flow.  It is not possible
    to compare two arbitrary functions in this way, so we represent
    partial states in a more concrete format: as a list of [id * nat]
    pairs. *)

Definition pe_state := list (id * nat).

(** The idea is that a variable [id] appears in the list if and only
    if we know its current [nat] value.  The [pe_lookup] function thus
    interprets this concrete representation.  (If the same variable
    [id] appears multiple times in the list, the first occurrence
    wins, but we will define our partial evaluator to never construct
    such a [pe_state].) *)

Fixpoint pe_lookup (pe_st : pe_state) (V:id) : option nat :=
  match pe_st with
  | [] => None
  | (V',n')::pe_st => if beq_id V V' then Some n'
                      else pe_lookup pe_st V
  end.

(** For example, [empty_pe_state] represents complete ignorance about
    every variable -- the function that maps every [id] to [None]. *)

Definition empty_pe_state : pe_state := [].

(** More generally, if the [list] representing a [pe_state] does not
    contain some [id], then that [pe_state] must map that [id] to
    [None].  Before we prove this fact, we first define a useful
    tactic for reasoning with [id] equality.  The tactic
        compare V V' SCase
    means to reason by cases whether [beq_id V V'] is [true] or
    [false].  In the case where [beq_id V V' = true], the tactic
    substitutes [V] for [V'] throughout. *)

Tactic Notation "compare" ident(i) ident(j) ident(c) :=
  let H := fresh "Heq" i j in
  destruct (beq_id i j) as [|]_eqn:H;
  [ Case_aux c "equal"; symmetry in H; apply beq_id_eq in H; subst j
  | Case_aux c "not equal" ].

Theorem pe_domain: forall pe_st V n,
  pe_lookup pe_st V = Some n ->
  true = existsb (beq_id V) (map (@fst _ _) pe_st).
Proof. intros pe_st V n H. induction pe_st as [| [V' n'] pe_st].
  Case "[]". inversion H.
  Case "::". simpl in H. simpl. compare V V' SCase; auto. Qed.

(** ** Arithmetic Expressions *)

(** Partial evaluation of [aexp] is straightforward -- it is basically
    the same as constant folding, [fold_constants_aexp], except that
    sometimes the partial state tells us the current value of a
    variable and we can replace it by a constant expression. *)

Fixpoint pe_aexp (pe_st : pe_state) (a : aexp) : aexp :=
  match a with
  | ANum n => ANum n
  | AId i => match pe_lookup pe_st i with (* <----- NEW *)
             | Some n => ANum n
             | None => AId i
             end
  | APlus a1 a2 =>
      match (pe_aexp pe_st a1, pe_aexp pe_st a2) with
      | (ANum n1, ANum n2) => ANum (n1 + n2)
      | (a1', a2') => APlus a1' a2'
      end
  | AMinus a1 a2 =>
      match (pe_aexp pe_st a1, pe_aexp pe_st a2) with
      | (ANum n1, ANum n2) => ANum (n1 - n2)
      | (a1', a2') => AMinus a1' a2'
      end
  | AMult a1 a2 =>
      match (pe_aexp pe_st a1, pe_aexp pe_st a2) with
      | (ANum n1, ANum n2) => ANum (n1 * n2)
      | (a1', a2') => AMult a1' a2'
      end
  end.

(** This partial evaluator folds constants but does not apply the
    associativity of addition. *)

Example test_pe_aexp1:
  pe_aexp [(X,3)] (APlus (APlus (AId X) (ANum 1)) (AId Y))
  = APlus (ANum 4) (AId Y).
Proof. reflexivity. Qed.

Example text_pe_aexp2:
  pe_aexp [(Y,3)] (APlus (APlus (AId X) (ANum 1)) (AId Y))
  = APlus (APlus (AId X) (ANum 1)) (ANum 3).
Proof. reflexivity. Qed.

(** Now, in what sense is [pe_aexp] correct?  It is reasonable to
    define the correctness of [pe_aexp] as follows: whenever a full
    state [st:state] is _consistent_ with a partial state
    [pe_st:pe_state] (in other words, every variable to which [pe_st]
    assigns a value is assigned the same value by [st]), evaluating
    [a] and evaluating [pe_aexp pe_st a] in [st] yields the same
    result.  This statement is indeed true. *)

Definition pe_consistent (st:state) (pe_st:pe_state) :=
  forall V n, Some n = pe_lookup pe_st V -> st V = n.

Theorem pe_aexp_correct_weak: forall st pe_st, pe_consistent st pe_st ->
  forall a, aeval st a = aeval st (pe_aexp pe_st a).
Proof. unfold pe_consistent. intros st pe_st H a.
  aexp_cases (induction a) Case; simpl;
    try reflexivity;
    try (destruct (pe_aexp pe_st a1);
         destruct (pe_aexp pe_st a2);
         rewrite IHa1; rewrite IHa2; reflexivity).
  (* Compared to fold_constants_aexp_sound,
     the only interesting case is AId *)
  Case "AId".
    remember (pe_lookup pe_st i) as l. destruct l.
    SCase "Some". rewrite H with (n:=n) by apply Heql. reflexivity.
    SCase "None". reflexivity.
Qed.

(** However, we will soon want our partial evaluator to remove
    assignments.  For example, it will simplify
        X ::= ANum 3; Y ::= AMinus (AId X) (AId Y); X ::= ANum 4
    to just
        Y ::= AMinus (ANum 3) (AId Y); X ::= ANum 4
    by delaying the assignment to [X] until the end.  To accomplish
    this simplification, we need the result of partial evaluating
        pe_aexp [(X,3)] (AMinus (AId X) (AId Y))
    to be equal to [AMinus (ANum 3) (AId Y)] and _not_ the original
    expression [AMinus (AId X) (AId Y)].  After all, it would be
    incorrect, not just inefficient, to transform
        X ::= ANum 3; Y ::= AMinus (AId X) (AId Y); X ::= ANum 4
    to
        Y ::= AMinus (AId X) (AId Y); X ::= ANum 4
    even though the output expressions [AMinus (ANum 3) (AId Y)] and
    [AMinus (AId X) (AId Y)] both satisfy the correctness criterion
    that we just proved.  Indeed, if we were to just define [pe_aexp
    pe_st a = a] then the theorem [pe_aexp_correct'] would already
    trivially hold.

    Instead, we want to prove that the [pe_aexp] is correct in a
    stronger sense: evaluating the expression produced by partial
    evaluation ([aeval st (pe_aexp pe_st a)]) must not depend on those
    parts of the full state [st] that are already specified in the
    partial state [pe_st].  To be more precise, let us define a
    function [pe_override], which updates [st] with the contents of
    [pe_st].  In other words, [pe_override] carries out the
    assignments listed in [pe_st] on top of [st]. *)

Fixpoint pe_override (st:state) (pe_st:pe_state) : state :=
  match pe_st with
  | [] => st
  | (V,n)::pe_st => update (pe_override st pe_st) V n
  end.

Example test_pe_override:
  pe_override (update empty_state Y 1) [(X,3),(Z,2)]
  = update (update (update empty_state Y 1) Z 2) X 3.
Proof. reflexivity. Qed.

(** Although [pe_override] operates on a concrete [list] representing
    a [pe_state], its behavior is defined entirely by the [pe_lookup]
    interpretation of the [pe_state]. *)

Theorem pe_override_correct: forall st pe_st V0,
  pe_override st pe_st V0 =
  match pe_lookup pe_st V0 with
  | Some n => n
  | None => st V0
  end.
Proof. intros. induction pe_st as [| [V n] pe_st]. reflexivity.
  simpl in *. unfold update. rewrite beq_id_sym.
  compare V0 V Case; auto. Qed.

(** We can relate [pe_consistent] to [pe_override] in two ways.
    First, overriding a state with a partial state always gives a
    state that is consistent with the partial state.  Second, if a
    state is already consistent with a partial state, then overriding
    the state with the partial state gives the same state. *)

Theorem pe_override_consistent: forall st pe_st,
  pe_consistent (pe_override st pe_st) pe_st.
Proof. intros st pe_st V n H. rewrite pe_override_correct.
  destruct (pe_lookup pe_st V); inversion H. reflexivity. Qed.

Theorem pe_consistent_override: forall st pe_st,
  pe_consistent st pe_st -> forall V, st V = pe_override st pe_st V.
Proof. intros st pe_st H V. rewrite pe_override_correct.
  remember (pe_lookup pe_st V) as l. destruct l; auto. Qed.

(** Now we can state and prove that [pe_aexp] is correct in the
    stronger sense that will help us define the rest of the partial
    evaluator.

    Intuitively, running a program using partial evaluation is a
    two-stage process.  In the first, _static_ stage, we partially
    evaluate the given program with respect to some partial state to
    get a _residual_ program.  In the second, _dynamic_ stage, we
    evaluate the residual program with respect to the rest of the
    state.  This dynamic state provides values for those variables
    that are unknown in the static (partial) state.  Thus, the
    residual program should be equivalent to _prepending_ the
    assignments listed in the partial state to the original program. *)

Theorem pe_aexp_correct: forall (pe_st:pe_state) (a:aexp) (st:state),
  aeval (pe_override st pe_st) a = aeval st (pe_aexp pe_st a).
Proof.
  intros pe_st a st.
  aexp_cases (induction a) Case; simpl;
    try reflexivity;
    try (destruct (pe_aexp pe_st a1);
         destruct (pe_aexp pe_st a2);
         rewrite IHa1; rewrite IHa2; reflexivity).
  (* Compared to fold_constants_aexp_sound, the only
     interesting case is AId. *)
  rewrite pe_override_correct. destruct (pe_lookup pe_st i); reflexivity.
Qed.

(** ** Boolean Expressions *)

(** The partial evaluation of boolean expressions is similar.  In
    fact, it is entirely analogous to the constant folding of boolean
    expressions, because our language has no boolean variables. *)

Fixpoint pe_bexp (pe_st : pe_state) (b : bexp) : bexp :=
  match b with
  | BTrue        => BTrue
  | BFalse       => BFalse
  | BEq a1 a2 =>
      match (pe_aexp pe_st a1, pe_aexp pe_st a2) with
      | (ANum n1, ANum n2) => if beq_nat n1 n2 then BTrue else BFalse
      | (a1', a2') => BEq a1' a2'
      end
  | BLe a1 a2 =>
      match (pe_aexp pe_st a1, pe_aexp pe_st a2) with
      | (ANum n1, ANum n2) => if ble_nat n1 n2 then BTrue else BFalse
      | (a1', a2') => BLe a1' a2'
      end
  | BNot b1 =>
      match (pe_bexp pe_st b1) with
      | BTrue => BFalse
      | BFalse => BTrue
      | b1' => BNot b1'
      end
  | BAnd b1 b2 =>
      match (pe_bexp pe_st b1, pe_bexp pe_st b2) with
      | (BTrue, BTrue) => BTrue
      | (BTrue, BFalse) => BFalse
      | (BFalse, BTrue) => BFalse
      | (BFalse, BFalse) => BFalse
      | (b1', b2') => BAnd b1' b2'
      end
  end.

Example test_pe_bexp1:
  pe_bexp [(X,3)] (BNot (BLe (AId X) (ANum 3)))
  = BFalse.
Proof. reflexivity. Qed.

Example test_pe_bexp2: forall b,
  b = BNot (BLe (AId X) (APlus (AId X) (ANum 1))) ->
  pe_bexp [] b = b.
Proof. intros b H. rewrite -> H. reflexivity. Qed.

(** The correctness of [pe_bexp] is analogous to the correctness of
    [pe_aexp] above. *)

Theorem pe_bexp_correct: forall (pe_st:pe_state) (b:bexp) (st:state),
  beval (pe_override st pe_st) b = beval st (pe_bexp pe_st b).
Proof.
  intros pe_st b st.
  bexp_cases (induction b) Case; simpl;
    try reflexivity;
    try (remember (pe_aexp pe_st a) as a';
         remember (pe_aexp pe_st a0) as a0';
         assert (Ha: aeval (pe_override st pe_st) a = aeval st a');
         assert (Ha0: aeval (pe_override st pe_st) a0 = aeval st a0');
           try (subst; apply pe_aexp_correct);
         destruct a'; destruct a0'; rewrite Ha; rewrite Ha0;
         simpl; try destruct (beq_nat n n0); try destruct (ble_nat n n0);
         reflexivity);
    try (destruct (pe_bexp pe_st b); rewrite IHb; reflexivity);
    try (destruct (pe_bexp pe_st b1);
         destruct (pe_bexp pe_st b2);
         rewrite IHb1; rewrite IHb2; reflexivity).
Qed.

(* ####################################################### *)
(** * Partial Evaluation of Commands, Without Loops *)

(** What about the partial evaluation of commands?  The analogy
    between partial evaluation and full evaluation continues: Just as
    full evaluation of a command turns an initial state into a final
    state, partial evaluation of a command turns an initial partial
    state into a final partial state.  The difference is that, because
    the state is partial, some parts of the command may not be
    executable at the static stage.  Therefore, just as [pe_aexp]
    returns a residual [aexp] and [pe_bexp] returns a residual [bexp]
    above, partially evaluating a command yields a residual command.

    Another way in which our partial evaluator is similar to a full
    evaluator is that it does not terminate on all commands.  It is
    not hard to build a partial evaluator that terminates on all
    commands; what is hard is building a partial evaluator that
    terminates on all commands yet automatically performs desired
    optimizations such as unrolling loops.  Often a partial evaluator
    can be coaxed into terminating more often and performing more
    optimizations by writing the source program differently so that
    the separation between static and dynamic information becomes more
    apparent.  Such coaxing is the art of _binding-time improvement_.
    The binding time of a variable tells when its value is known --
    either "static", or "dynamic."

    Anyway, for now we will just live with the fact that our partial
    evaluator is not a total function from the source command and the
    initial partial state to the residual command and the final
    partial state.  To model this non-termination, just as with the
    full evaluation of commands, we use an inductively defined
    relation.  We write
        c1 / st || c1' / st'
    to mean that partially evaluating the source command [c1] in the
    initial partial state [st] yields the residual command [c1'] and
    the final partial state [st'].  For example, we want something like
        (X ::= ANum 3 ; Y ::= AMult (AId Z) (APlus (AId X) (AId X)))
        / [] || (Y ::= AMult (AId Z) (ANum 6)) / [(X,3)]
    to hold.  The assignment to [X] appears in the final partial state,
    not the residual command. *)

(** ** Assignment *)

(** Let's start by considering how to partially evaluate an
    assignment.  The two assignments in the source program above needs
    to be treated differently.  The first assignment [X ::= ANum 3],
    is _static_: its right-hand-side is a constant (more generally,
    simplifies to a constant), so we should update our partial state
    at [X] to [3] and produce no residual code.  (Actually, we produce
    a residual [SKIP].)  The second assignment [Y ::= AMult (AId Z)
    (APlus (AId X) (AId X))] is _dynamic_: its right-hand-side does
    not simplify to a constant, so we should leave it in the residual
    code and remove [Y], if present, from our partial state.  To
    implement these two cases, we define the functions [pe_add] and
    [pe_remove].  Like [pe_override] above, these functions operate on
    a concrete [list] representing a [pe_state], but the theorems
    [pe_add_correct] and [pe_remove_correct] specify their behavior by
    the [pe_lookup] interpretation of the [pe_state]. *)

Fixpoint pe_remove (pe_st:pe_state) (V:id) : pe_state :=
  match pe_st with
  | [] => []
  | (V',n')::pe_st => if beq_id V V' then pe_remove pe_st V
                      else (V',n') :: pe_remove pe_st V
  end.

Theorem pe_remove_correct: forall pe_st V V0,
  pe_lookup (pe_remove pe_st V) V0
  = if beq_id V V0 then None else pe_lookup pe_st V0.
Proof. intros pe_st V V0. induction pe_st as [| [V' n'] pe_st].
  Case "[]". destruct (beq_id V V0); reflexivity.
  Case "::". simpl. compare V V' SCase.
    SCase "equal". rewrite IHpe_st.
      replace (beq_id V0 V) with (beq_id V V0) by apply beq_id_sym.
      destruct (beq_id V V0); reflexivity.
    SCase "not equal". simpl. compare V0 V' SSCase.
      SSCase "equal". rewrite HeqVV'. reflexivity.
      SSCase "not equal". rewrite IHpe_st. reflexivity.
Qed.

Definition pe_add (pe_st:pe_state) (V:id) (n:nat) : pe_state :=
  (V,n) :: pe_remove pe_st V.

Theorem pe_add_correct: forall pe_st V n V0,
  pe_lookup (pe_add pe_st V n) V0
  = if beq_id V V0 then Some n else pe_lookup pe_st V0.
Proof. intros pe_st V n V0. unfold pe_add. simpl. rewrite beq_id_sym.
  compare V V0 Case.
  Case "equal". reflexivity.
  Case "not equal". rewrite pe_remove_correct. rewrite HeqVV0. reflexivity.
Qed.

(** We will use the two theorems below to show that our partial
    evaluator correctly deals with dynamic assignments and static
    assignments, respectively. *)

Theorem pe_override_update_remove: forall st pe_st V n,
  update (pe_override st pe_st) V n =
  pe_override (update st V n) (pe_remove pe_st V).
Proof. intros st pe_st V n. apply functional_extensionality. intros V0.
  unfold update. rewrite !pe_override_correct. rewrite pe_remove_correct.
  destruct (beq_id V V0); reflexivity. Qed.

Theorem pe_override_update_add: forall st pe_st V n,
  update (pe_override st pe_st) V n =
  pe_override st (pe_add pe_st V n).
Proof. intros st pe_st V n. apply functional_extensionality. intros V0.
  unfold update. rewrite !pe_override_correct. rewrite pe_add_correct.
  destruct (beq_id V V0); reflexivity. Qed.

(** ** Conditional *)

(** Trickier than assignments to partially evaluate is the
    conditional, [IFB b1 THEN c1 ELSE c2 FI].  If [b1] simplifies to
    [BTrue] or [BFalse] then it's easy: we know which branch will be
    taken, so just take that branch.  If [b1] does not simplify to a
    constant, then we need to take both branches, and the final
    partial state may differ between the two branches!

    The following program illustrates the difficulty:
        X ::= ANum 3;
        IFB BLe (AId Y) (ANum 4) THEN
            Y ::= ANum 4;
            IFB BEq (AId X) (AId Y) THEN Y ::= ANum 999 ELSE SKIP FI
        ELSE SKIP FI
    Suppose the initial partial state is empty.  We don't know
    statically how [Y] compares to [4], so we must partially evaluate
    both branches of the (outer) conditional.  On the [THEN] branch,
    we know that [Y] is set to [4] and can even use that knowledge to
    simplify the code somewhat.  On the [ELSE] branch, we still don't
    know the exact value of [Y] at the end.  What should the final
    partial state and residual program be?

    One way to handle such a dynamic conditional is to take the
    intersection of the final partial states of the two branches.  In
    this example, we take the intersection of [(Y,4),(X,3)] and
    [(X,3)], so the overall final partial state is [(X,3)].  To
    compensate for forgetting that [Y] is [4], we need to add an
    assignment [Y ::= ANum 4] to the end of the [THEN] branch.  So,
    the residual program will be something like
        SKIP;
        IFB BLe (AId Y) (ANum 4) THEN
            SKIP;
            SKIP;
            Y ::= ANum 4
        ELSE SKIP FI

    Programming this case in Coq calls for several auxiliary
    functions: we need to compute the intersection of two [pe_state]s
    and turn their difference into sequences of assignments.

    First, we show how to compute whether two [pe_state]s to disagree
    at a given variable.  In the theorem [pe_disagree_domain], we
    prove that two [pe_state]s can only disagree at variables that
    appear in at least one of them. *)

Definition pe_disagree_at (pe_st1 pe_st2 : pe_state) (V:id) : bool :=
  match pe_lookup pe_st1 V, pe_lookup pe_st2 V with
  | Some x, Some y => negb (beq_nat x y)
  | None, None => false
  | _, _ => true
  end.

Lemma existsb_app: forall X (f:X->bool) l1 l2,
  existsb f (l1 ++ l2) = orb (existsb f l1) (existsb f l2).
Proof. intros X f l1 l2. induction l1. reflexivity.
  simpl. rewrite IHl1. rewrite orb_assoc. reflexivity. Qed.

Theorem pe_disagree_domain: forall (pe_st1 pe_st2 : pe_state) (V:id),
  true = pe_disagree_at pe_st1 pe_st2 V ->
  true = existsb (beq_id V) (map (@fst _ _) pe_st1 ++
                             map (@fst _ _) pe_st2).
Proof. unfold pe_disagree_at. intros pe_st1 pe_st2 V H.
  rewrite existsb_app. symmetry. apply orb_true_intro.
  remember (pe_lookup pe_st1 V) as lookup1.
  destruct lookup1 as [n1|]. left. symmetry. apply pe_domain with n1. auto.
  remember (pe_lookup pe_st2 V) as lookup2.
  destruct lookup2 as [n2|]. right. symmetry. apply pe_domain with n2. auto.
  inversion H. Qed.

(** We define the [pe_compare] function to list the variables where
    two given [pe_state]s disagree.  This list is exact, according to
    the theorem [pe_compare_correct]: a variable appears on the list
    if and only if the two given [pe_state]s disagree at that
    variable.  Furthermore, we use the [pe_unique] function to
    eliminate duplicates from the list. *)

Fixpoint pe_unique (l : list id) : list id :=
  match l with
  | [] => []
  | x::l => x :: filter (fun y => negb (beq_id x y)) (pe_unique l)
  end.

Lemma existsb_beq_id_filter: forall V f l,
  existsb (beq_id V) (filter f l) = andb (existsb (beq_id V) l) (f V).
Proof. intros V f l. induction l as [| h l].
  Case "[]". reflexivity.
  Case "h::l". simpl. remember (f h) as fh. destruct fh.
    SCase "true = f h". simpl. rewrite IHl. compare V h SSCase.
      rewrite <- Heqfh. reflexivity. reflexivity.
    SCase "false = f h". rewrite IHl. compare V h SSCase.
      rewrite <- Heqfh. rewrite !andb_false_r. reflexivity. reflexivity.
Qed.

Theorem pe_unique_correct: forall l x,
  existsb (beq_id x) l = existsb (beq_id x) (pe_unique l).
Proof. intros l x. induction l as [| h t]. reflexivity.
  simpl in *. compare x h Case.
  Case "equal". reflexivity.
  Case "not equal".
    rewrite -> existsb_beq_id_filter, <- IHt, -> beq_id_sym, -> Heqxh,
            -> andb_true_r. reflexivity. Qed.

Definition pe_compare (pe_st1 pe_st2 : pe_state) : list id :=
  pe_unique (filter (pe_disagree_at pe_st1 pe_st2)
    (map (@fst _ _) pe_st1 ++ map (@fst _ _) pe_st2)).

Theorem pe_compare_correct: forall pe_st1 pe_st2 V,
  pe_lookup pe_st1 V = pe_lookup pe_st2 V <->
  false = existsb (beq_id V) (pe_compare pe_st1 pe_st2).
Proof. intros pe_st1 pe_st2 V.
  unfold pe_compare. rewrite <- pe_unique_correct, -> existsb_beq_id_filter.
  split; intros Heq.
  Case "->".
    symmetry. apply andb_false_intro2. unfold pe_disagree_at. rewrite Heq.
    destruct (pe_lookup pe_st2 V).
    rewrite <- beq_nat_refl. reflexivity.
    reflexivity.
  Case "<-".
    assert (Hagree: pe_disagree_at pe_st1 pe_st2 V = false).
      SCase "Proof of assertion".
      remember (pe_disagree_at pe_st1 pe_st2 V) as disagree.
      destruct disagree; [| reflexivity].
      rewrite -> andb_true_r, <- pe_disagree_domain in Heq.
      inversion Heq.
      apply Heqdisagree.
    unfold pe_disagree_at in Hagree.
    destruct (pe_lookup pe_st1 V) as [n1|];
    destruct (pe_lookup pe_st2 V) as [n2|];
      try reflexivity; try solve by inversion.
    rewrite beq_nat_eq with n1 n2. reflexivity.
    rewrite <- negb_involutive. rewrite Hagree. reflexivity. Qed.

(** The intersection of two partial states is the result of removing
    from one of them all the variables where the two disagree.  We
    define the function [pe_removes], in terms of [pe_remove] above,
    to perform such a removal of a whole list of variables at once.

    The theorem [pe_compare_removes] testifies that the [pe_lookup]
    interpretation of the result of this intersection operation is the
    same no matter which of the two partial states we remove the
    variables from.  Because [pe_override] only depends on the
    [pe_lookup] interpretation of partial states, [pe_override] also
    does not care which of the two partial states we remove the
    variables from; that theorem [pe_compare_override] is used in the
    correctness proof shortly. *)

Fixpoint pe_removes (pe_st:pe_state) (ids : list id) : pe_state :=
  match ids with
  | [] => pe_st
  | V::ids => pe_remove (pe_removes pe_st ids) V
  end.

Theorem pe_removes_correct: forall pe_st ids V,
  pe_lookup (pe_removes pe_st ids) V =
  if existsb (beq_id V) ids then None else pe_lookup pe_st V.
Proof. intros pe_st ids V. induction ids as [| V' ids]. reflexivity.
  simpl. rewrite pe_remove_correct. rewrite IHids.
  replace (beq_id V' V) with (beq_id V V') by apply beq_id_sym.
  destruct (beq_id V V'); destruct (existsb (beq_id V) ids); reflexivity.
Qed.

Theorem pe_compare_removes: forall pe_st1 pe_st2 V,
  pe_lookup (pe_removes pe_st1 (pe_compare pe_st1 pe_st2)) V =
  pe_lookup (pe_removes pe_st2 (pe_compare pe_st1 pe_st2)) V.
Proof. intros pe_st1 pe_st2 V. rewrite !pe_removes_correct.
  remember (existsb (beq_id V) (pe_compare pe_st1 pe_st2)) as b.
  destruct b. reflexivity.
  apply pe_compare_correct in Heqb. apply Heqb. Qed.

Theorem pe_compare_override: forall pe_st1 pe_st2 st,
  pe_override st (pe_removes pe_st1 (pe_compare pe_st1 pe_st2)) =
  pe_override st (pe_removes pe_st2 (pe_compare pe_st1 pe_st2)).
Proof. intros. apply functional_extensionality. intros V.
  rewrite !pe_override_correct. rewrite pe_compare_removes. reflexivity.
Qed.

(** Finally, we define an [assign] function to turn the difference
    between two partial states into a sequence of assignment commands.
    More precisely, [assign pe_st ids] generates an assignment command
    for each variable listed in [ids]. *)

Fixpoint assign (pe_st : pe_state) (ids : list id) : com :=
  match ids with
  | [] => SKIP
  | V::ids => match pe_lookup pe_st V with
              | Some n => (assign pe_st ids; V ::= ANum n)
              | None => assign pe_st ids
              end
  end.

(** The command generated by [assign] always terminates, because it is
    just a sequence of assignments.  The (total) function [assigned]
    below computes the effect of the command on the (dynamic state).
    The theorem [assign_removes] then confirms that the generated
    assignments perfectly compensate for removing the variables from
    the partial state. *)

Definition assigned (pe_st:pe_state) (ids : list id) (st:state) : state :=
  fun V => match existsb (beq_id V) ids, pe_lookup pe_st V with
           | true, Some n => n
           | _, _ => st V
           end.

Theorem assign_removes: forall pe_st ids st,
  pe_override st pe_st =
  pe_override (assigned pe_st ids st) (pe_removes pe_st ids).
Proof. intros pe_st ids st. apply functional_extensionality. intros V.
  rewrite !pe_override_correct. rewrite pe_removes_correct. unfold assigned.
  destruct (existsb (beq_id V)); destruct (pe_lookup pe_st V); reflexivity.
Qed.

Lemma ceval_extensionality: forall c st st1 st2,
  c / st || st1 -> (forall V, st1 V = st2 V) -> c / st || st2.
Proof. intros c st st1 st2 H Heq.
  apply functional_extensionality in Heq. rewrite <- Heq. apply H. Qed.

Theorem eval_assign: forall pe_st ids st,
  assign pe_st ids / st || assigned pe_st ids st.
Proof. intros pe_st ids st. induction ids as [| V ids]; simpl.
  Case "[]". eapply ceval_extensionality. apply E_Skip. reflexivity.
  Case "V::ids".
    remember (pe_lookup pe_st V) as lookup. destruct lookup.
    SCase "Some". eapply E_Seq. apply IHids. unfold assigned. simpl.
      eapply ceval_extensionality. apply E_Ass. simpl. reflexivity.
      intros V0. unfold update. rewrite beq_id_sym. compare V0 V SSCase.
      SSCase "equal". rewrite <- Heqlookup. reflexivity.
      SSCase "not equal". reflexivity.
    SCase "None". eapply ceval_extensionality. apply IHids.
      unfold assigned. intros V0. simpl. compare V0 V SSCase.
      SSCase "equal". rewrite <- Heqlookup.
        destruct (existsb (beq_id V0) ids); reflexivity.
      SSCase "not equal". reflexivity. Qed.

(** ** The Partial Evaluation Relation *)

(** At long last, we can define a partial evaluator for commands
    without loops, as an inductive relation!  The inequality
    conditions in [PE_AssDynamic] and [PE_If] are just to keep the
    partial evaluator deterministic; they are not required for
    correctness. *)

Reserved Notation "c1 '/' st '||' c1' '/' st'"
  (at level 40, st at level 39, c1' at level 39).

Inductive pe_com : com -> pe_state -> com -> pe_state -> Prop :=
  | PE_Skip : forall pe_st,
      SKIP / pe_st || SKIP / pe_st
  | PE_AssStatic : forall pe_st a1 n1 l,
      pe_aexp pe_st a1 = ANum n1 ->
      (l ::= a1) / pe_st || SKIP / pe_add pe_st l n1
  | PE_AssDynamic : forall pe_st a1 a1' l,
      pe_aexp pe_st a1 = a1' ->
      (forall n, a1' <> ANum n) ->
      (l ::= a1) / pe_st || (l ::= a1') / pe_remove pe_st l
  | PE_Seq : forall pe_st pe_st' pe_st'' c1 c2 c1' c2',
      c1 / pe_st  || c1' / pe_st' ->
      c2 / pe_st' || c2' / pe_st'' ->
      (c1 ; c2) / pe_st || (c1' ; c2') / pe_st''
  | PE_IfTrue : forall pe_st pe_st' b1 c1 c2 c1',
      pe_bexp pe_st b1 = BTrue ->
      c1 / pe_st || c1' / pe_st' ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st || c1' / pe_st'
  | PE_IfFalse : forall pe_st pe_st' b1 c1 c2 c2',
      pe_bexp pe_st b1 = BFalse ->
      c2 / pe_st || c2' / pe_st' ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st || c2' / pe_st'
  | PE_If : forall pe_st pe_st1 pe_st2 b1 c1 c2 c1' c2',
      pe_bexp pe_st b1 <> BTrue ->
      pe_bexp pe_st b1 <> BFalse ->
      c1 / pe_st || c1' / pe_st1 ->
      c2 / pe_st || c2' / pe_st2 ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st
        || (IFB pe_bexp pe_st b1
             THEN c1' ; assign pe_st1 (pe_compare pe_st1 pe_st2)
             ELSE c2' ; assign pe_st2 (pe_compare pe_st1 pe_st2) FI)
            / pe_removes pe_st1 (pe_compare pe_st1 pe_st2)

  where "c1 '/' st '||' c1' '/' st'" := (pe_com c1 st c1' st').

Tactic Notation "pe_com_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "PE_Skip"
  | Case_aux c "PE_AssStatic" | Case_aux c "PE_AssDynamic"
  | Case_aux c "PE_Seq"
  | Case_aux c "PE_IfTrue" | Case_aux c "PE_IfFalse" | Case_aux c "PE_If" ].

Hint Constructors pe_com.
Hint Constructors ceval.

(** ** Examples *)

(** Below are some examples of using the partial evaluator.  To make
    the [pe_com] relation actually usable for automatic partial
    evaluation, we would need to define more automation tactics in
    Coq.  That is not hard to do, but it is not needed here. *)

Example pe_example1:
  (X ::= ANum 3 ; Y ::= AMult (AId Z) (APlus (AId X) (AId X)))
  / [] || (SKIP; Y ::= AMult (AId Z) (ANum 6)) / [(X,3)].
Proof. eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  eapply PE_AssDynamic. reflexivity. intros n H. inversion H. Qed.

Example pe_example2:
  (X ::= ANum 3 ; IFB BLe (AId X) (ANum 4) THEN X ::= ANum 4 ELSE SKIP FI)
  / [] || (SKIP; SKIP) / [(X,4)].
Proof. eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  eapply PE_IfTrue. reflexivity.
  eapply PE_AssStatic. reflexivity. Qed.

Example pe_example3:
  (X ::= ANum 3;
   IFB BLe (AId Y) (ANum 4) THEN
     Y ::= ANum 4;
     IFB BEq (AId X) (AId Y) THEN Y ::= ANum 999 ELSE SKIP FI
   ELSE SKIP FI) / []
  || (SKIP;
       IFB BLe (AId Y) (ANum 4) THEN
         (SKIP; SKIP); (SKIP; Y ::= ANum 4)
       ELSE SKIP; SKIP FI)
      / [(X,3)].
Proof. erewrite f_equal2 with (f := fun c st => _ / _ || c / st).
  eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  eapply PE_If; intuition eauto; try solve by inversion.
  econstructor. eapply PE_AssStatic. reflexivity.
  eapply PE_IfFalse. reflexivity. econstructor.
  reflexivity. reflexivity. Qed.

(** ** Correctness of Partial Evaluation *)

(** Finally let's prove that this partial evaluator is correct! *)

Reserved Notation "c' '/' pe_st' '/' st '||' st''"
  (at level 40, pe_st' at level 39, st at level 39).

Inductive pe_ceval
  (c':com) (pe_st':pe_state) (st:state) (st'':state) : Prop :=
  | pe_ceval_intro : forall st',
    c' / st || st' ->
    pe_override st' pe_st' = st'' ->
    c' / pe_st' / st || st''
  where "c' '/' pe_st' '/' st '||' st''" := (pe_ceval c' pe_st' st st'').

Hint Constructors pe_ceval.

Theorem pe_com_complete:
  forall c pe_st pe_st' c', c / pe_st || c' / pe_st' ->
  forall st st'',
  (c / pe_override st pe_st || st'') ->
  (c' / pe_st' / st || st'').
Proof. intros c pe_st pe_st' c' Hpe.
  pe_com_cases (induction Hpe) Case; intros st st'' Heval;
  try (inversion Heval; subst;
       try (rewrite -> pe_bexp_correct, -> H in *; solve by inversion);
       []);
  eauto.
  Case "PE_AssStatic". econstructor. econstructor.
    rewrite -> pe_aexp_correct. rewrite <- pe_override_update_add.
    rewrite -> H. reflexivity.
  Case "PE_AssDynamic". econstructor. econstructor. reflexivity.
    rewrite -> pe_aexp_correct. rewrite <- pe_override_update_remove.
    reflexivity.
  Case "PE_Seq".
    edestruct IHHpe1. eassumption. subst.
    edestruct IHHpe2. eassumption.
    eauto.
  Case "PE_If". inversion Heval; subst.
    SCase "E'IfTrue". edestruct IHHpe1. eassumption.
      econstructor. apply E_IfTrue. rewrite <- pe_bexp_correct. assumption.
      eapply E_Seq. eassumption. apply eval_assign.
      rewrite <- assign_removes. eassumption.
    SCase "E_IfFalse". edestruct IHHpe2. eassumption.
      econstructor. apply E_IfFalse. rewrite <- pe_bexp_correct. assumption.
      eapply E_Seq. eassumption. apply eval_assign.
      rewrite -> pe_compare_override.
      rewrite <- assign_removes. eassumption.
Qed.

Theorem pe_com_sound:
  forall c pe_st pe_st' c', c / pe_st || c' / pe_st' ->
  forall st st'',
  (c' / pe_st' / st || st'') ->
  (c / pe_override st pe_st || st'').
Proof. intros c pe_st pe_st' c' Hpe.
  pe_com_cases (induction Hpe) Case;
    intros st st'' [st' Heval Heq];
    try (inversion Heval; []; subst); auto.
  Case "PE_AssStatic". rewrite <- pe_override_update_add. apply E_Ass.
    rewrite -> pe_aexp_correct. rewrite -> H. reflexivity.
  Case "PE_AssDynamic". rewrite <- pe_override_update_remove. apply E_Ass.
    rewrite <- pe_aexp_correct. reflexivity.
  Case "PE_Seq". eapply E_Seq; eauto.
  Case "PE_IfTrue". apply E_IfTrue.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity. eauto.
  Case "PE_IfFalse". apply E_IfFalse.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity. eauto.
  Case "PE_If".
    inversion Heval; subst; inversion H7;
      (eapply ceval_deterministic in H8; [| apply eval_assign]); subst.
    SCase "E_IfTrue".
      apply E_IfTrue. rewrite -> pe_bexp_correct. assumption.
      rewrite <- assign_removes. eauto.
    SCase "E_IfFalse".
      rewrite -> pe_compare_override.
      apply E_IfFalse. rewrite -> pe_bexp_correct. assumption.
      rewrite <- assign_removes. eauto.
Qed.

(** The main theorem. Thanks to David Menendez for this formulation! *)

Corollary pe_com_correct:
  forall c pe_st pe_st' c', c / pe_st || c' / pe_st' ->
  forall st st'',
  (c / pe_override st pe_st || st'') <->
  (c' / pe_st' / st || st'').
Proof. intros c pe_st pe_st' c' H st st''. split.
  Case "->". apply pe_com_complete. apply H.
  Case "<-". apply pe_com_sound. apply H.
Qed.

(* ####################################################### *)
(** * Partial Evaluation of Loops *)

(** It may seem straightforward at first glance to extend the partial
    evaluation relation [pe_com] above to loops.  Indeed, many loops
    are easy to deal with.  Considered this repeated-squaring loop,
    for example:
        WHILE BLe (ANum 1) (AId X) DO
            Y ::= AMult (AId Y) (AId Y);
            X ::= AMinus (AId X) (ANum 1)
        END
    If we know neither [X] nor [Y] statically, then the entire loop is
    dynamic and the residual command should be the same.  If we know
    [X] but not [Y], then the loop can be unrolled all the way and the
    residual command should be
        Y ::= AMult (AId Y) (AId Y);
        Y ::= AMult (AId Y) (AId Y);
        Y ::= AMult (AId Y) (AId Y)
    if [X] is initially [3] (and finally [0]).  In general, a loop is
    easy to partially evaluate if the final partial state of the loop
    body is equal to the initial state, or if its guard condition is
    static.

    But there are other loops for which it is hard to express the
    residual program we want in Imp.  For example, take this program
    for checking if [Y] is even or odd:
        X ::= ANum 0;
        WHILE BLe (ANum 1) (AId Y) DO
            Y ::= AMinus (AId Y) (ANum 1);
            X ::= AMinus (ANum 1) (AId X)
        END
    The value of [X] alternates between [0] and [1] during the loop.
    Ideally, we would like to unroll this loop, not all the way but
    _two-fold_, into something like
        WHILE BLe (ANum 1) (AId Y) DO
            Y ::= AMinus (AId Y) (ANum 1);
            IF BLe (ANum 1) (AId Y) THEN
                Y ::= AMinus (AId Y) (ANum 1)
            ELSE
                X ::= ANum 1; EXIT
            FI
        END;
        X ::= ANum 0
    Unfortunately, there is no [EXIT] command in Imp.  Without
    extending the range of control structures available in our
    language, the best we can do is to repeat loop-guard tests or add
    flag variables.  Neither option is terribly attractive.

    Still, as a digression, below is an attempt at performing partial
    evaluation on Imp commands.  We add one more command argument
    [c''] to the [pe_com] relation, which keeps track of a loop to
    roll up. *)

Module Loop.

Reserved Notation "c1 '/' st '||' c1' '/' st' '/' c''"
  (at level 40, st at level 39, c1' at level 39, st' at level 39).

Inductive pe_com : com -> pe_state -> com -> pe_state -> com -> Prop :=
  | PE_Skip : forall pe_st,
      SKIP / pe_st || SKIP / pe_st / SKIP
  | PE_AssStatic : forall pe_st a1 n1 l,
      pe_aexp pe_st a1 = ANum n1 ->
      (l ::= a1) / pe_st || SKIP / pe_add pe_st l n1 / SKIP
  | PE_AssDynamic : forall pe_st a1 a1' l,
      pe_aexp pe_st a1 = a1' ->
      (forall n, a1' <> ANum n) ->
      (l ::= a1) / pe_st || (l ::= a1') / pe_remove pe_st l / SKIP
  | PE_Seq : forall pe_st pe_st' pe_st'' c1 c2 c1' c2' c'',
      c1 / pe_st  || c1' / pe_st' / SKIP ->
      c2 / pe_st' || c2' / pe_st'' / c'' ->
      (c1 ; c2) / pe_st || (c1' ; c2') / pe_st'' / c''
  | PE_IfTrue : forall pe_st pe_st' b1 c1 c2 c1' c'',
      pe_bexp pe_st b1 = BTrue ->
      c1 / pe_st || c1' / pe_st' / c'' ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st || c1' / pe_st' / c''
  | PE_IfFalse : forall pe_st pe_st' b1 c1 c2 c2' c'',
      pe_bexp pe_st b1 = BFalse ->
      c2 / pe_st || c2' / pe_st' / c'' ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st || c2' / pe_st' / c''
  | PE_If : forall pe_st pe_st1 pe_st2 b1 c1 c2 c1' c2' c'',
      pe_bexp pe_st b1 <> BTrue ->
      pe_bexp pe_st b1 <> BFalse ->
      c1 / pe_st || c1' / pe_st1 / c'' ->
      c2 / pe_st || c2' / pe_st2 / c'' ->
      (IFB b1 THEN c1 ELSE c2 FI) / pe_st
        || (IFB pe_bexp pe_st b1
             THEN c1' ; assign pe_st1 (pe_compare pe_st1 pe_st2)
             ELSE c2' ; assign pe_st2 (pe_compare pe_st1 pe_st2) FI)
            / pe_removes pe_st1 (pe_compare pe_st1 pe_st2)
            / c''
  | PE_WhileEnd : forall pe_st b1 c1,
      pe_bexp pe_st b1 = BFalse ->
      (WHILE b1 DO c1 END) / pe_st || SKIP / pe_st / SKIP
  | PE_WhileLoop : forall pe_st pe_st' pe_st'' b1 c1 c1' c2' c2'',
      pe_bexp pe_st b1 = BTrue ->
      c1 / pe_st || c1' / pe_st' / SKIP ->
      (WHILE b1 DO c1 END) / pe_st' || c2' / pe_st'' / c2'' ->
      pe_compare pe_st pe_st'' <> [] ->
      (WHILE b1 DO c1 END) / pe_st || (c1';c2') / pe_st'' / c2''
  | PE_While : forall pe_st pe_st' pe_st'' b1 c1 c1' c2' c2'',
      pe_bexp pe_st b1 <> BFalse ->
      pe_bexp pe_st b1 <> BTrue ->
      c1 / pe_st || c1' / pe_st' / SKIP ->
      (WHILE b1 DO c1 END) / pe_st' || c2' / pe_st'' / c2'' ->
      pe_compare pe_st pe_st'' <> [] ->
      (c2'' = SKIP \/ c2'' = WHILE b1 DO c1 END) ->
      (WHILE b1 DO c1 END) / pe_st
        || (IFB pe_bexp pe_st b1
             THEN c1'; c2'; assign pe_st'' (pe_compare pe_st pe_st'')
             ELSE assign pe_st (pe_compare pe_st pe_st'') FI)
            / pe_removes pe_st (pe_compare pe_st pe_st'')
            / c2''
  | PE_WhileFixedEnd : forall pe_st b1 c1,
      pe_bexp pe_st b1 <> BFalse ->
      (WHILE b1 DO c1 END) / pe_st || SKIP / pe_st / (WHILE b1 DO c1 END)
  | PE_WhileFixedLoop : forall pe_st pe_st' pe_st'' b1 c1 c1' c2',
      pe_bexp pe_st b1 = BTrue ->
      c1 / pe_st || c1' / pe_st' / SKIP ->
      (WHILE b1 DO c1 END) / pe_st'
        || c2' / pe_st'' / (WHILE b1 DO c1 END) ->
      pe_compare pe_st pe_st'' = [] ->
      (WHILE b1 DO c1 END) / pe_st
        || (WHILE BTrue DO SKIP END) / pe_st / SKIP
      (* Because we have an infinite loop, we should actually
         start to throw away the rest of the program:
         (WHILE b1 DO c1 END) / pe_st
         || SKIP / pe_st / (WHILE BTrue DO SKIP END) *)
  | PE_WhileFixed : forall pe_st pe_st' pe_st'' b1 c1 c1' c2',
      pe_bexp pe_st b1 <> BFalse ->
      pe_bexp pe_st b1 <> BTrue ->
      c1 / pe_st || c1' / pe_st' / SKIP ->
      (WHILE b1 DO c1 END) / pe_st'
        || c2' / pe_st'' / (WHILE b1 DO c1 END) ->
      pe_compare pe_st pe_st'' = [] ->
      (WHILE b1 DO c1 END) / pe_st
        || (WHILE pe_bexp pe_st b1 DO c1'; c2' END) / pe_st / SKIP

  where "c1 '/' st '||' c1' '/' st' '/' c''" := (pe_com c1 st c1' st' c'').

Tactic Notation "pe_com_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "PE_Skip"
  | Case_aux c "PE_AssStatic" | Case_aux c "PE_AssDynamic"
  | Case_aux c "PE_Seq"
  | Case_aux c "PE_IfTrue" | Case_aux c "PE_IfFalse" | Case_aux c "PE_If"
  | Case_aux c "PE_WhileEnd" | Case_aux c "PE_WhileLoop"
  | Case_aux c "PE_While" | Case_aux c "PE_WhileFixedEnd"
  | Case_aux c "PE_WhileFixedLoop" | Case_aux c "PE_WhileFixed" ].

Hint Constructors pe_com.

(** ** Examples *)

Tactic Notation "step" ident(i) :=
  (eapply i; intuition eauto; try solve by inversion);
  repeat (try eapply PE_Seq;
          try (eapply PE_AssStatic; simpl; reflexivity);
          try (eapply PE_AssDynamic;
               [ simpl; reflexivity
               | intuition eauto; solve by inversion ])).

Definition square_loop: com :=
  WHILE BLe (ANum 1) (AId X) DO
    Y ::= AMult (AId Y) (AId Y);
    X ::= AMinus (AId X) (ANum 1)
  END.

Example pe_loop_example1:
  square_loop / []
  || (WHILE BLe (ANum 1) (AId X) DO
         (Y ::= AMult (AId Y) (AId Y);
          X ::= AMinus (AId X) (ANum 1)); SKIP
       END) / [] / SKIP.
Proof. erewrite f_equal2 with (f := fun c st => _ / _ || c / st / SKIP).
  step PE_WhileFixed. step PE_WhileFixedEnd. reflexivity.
  reflexivity. reflexivity. Qed.

Example pe_loop_example2:
  (X ::= ANum 3; square_loop) / []
  || (SKIP;
       (Y ::= AMult (AId Y) (AId Y); SKIP);
       (Y ::= AMult (AId Y) (AId Y); SKIP);
       (Y ::= AMult (AId Y) (AId Y); SKIP);
       SKIP) / [(X,0)] / SKIP.
Proof. erewrite f_equal2 with (f := fun c st => _ / _ || c / st / SKIP).
  eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  step PE_WhileLoop.
  step PE_WhileLoop.
  step PE_WhileLoop.
  step PE_WhileEnd.
  inversion H. inversion H. inversion H.
  reflexivity. reflexivity. Qed.

Example pe_loop_example3:
  (Z ::= ANum 3; subtract_slowly) / []
  || (SKIP;
       IFB BNot (BEq (AId X) (ANum 0)) THEN
         (SKIP; X ::= AMinus (AId X) (ANum 1));
         IFB BNot (BEq (AId X) (ANum 0)) THEN
           (SKIP; X ::= AMinus (AId X) (ANum 1));
           IFB BNot (BEq (AId X) (ANum 0)) THEN
             (SKIP; X ::= AMinus (AId X) (ANum 1));
             WHILE BNot (BEq (AId X) (ANum 0)) DO
               (SKIP; X ::= AMinus (AId X) (ANum 1)); SKIP
             END;
             SKIP; Z ::= ANum 0
           ELSE SKIP; Z ::= ANum 1 FI; SKIP
         ELSE SKIP; Z ::= ANum 2 FI; SKIP
       ELSE SKIP; Z ::= ANum 3 FI) / [] / SKIP.
Proof. erewrite f_equal2 with (f := fun c st => _ / _ || c / st / SKIP).
  eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  step PE_While.
  step PE_While.
  step PE_While.
  step PE_WhileFixed.
  step PE_WhileFixedEnd.
  reflexivity. inversion H. inversion H. inversion H.
  reflexivity. reflexivity. Qed.

Example pe_loop_example4:
  (X ::= ANum 0;
   WHILE BLe (AId X) (ANum 2) DO
     X ::= AMinus (ANum 1) (AId X)
   END) / [] || (SKIP; WHILE BTrue DO SKIP END) / [(X,0)] / SKIP.
Proof. erewrite f_equal2 with (f := fun c st => _ / _ || c / st / SKIP).
  eapply PE_Seq. eapply PE_AssStatic. reflexivity.
  step PE_WhileFixedLoop.
  step PE_WhileLoop.
  step PE_WhileFixedEnd.
  inversion H. reflexivity. reflexivity. reflexivity. Qed.

(** ** Correctness *)

(** Because this partial evaluator can unroll a loop n-fold where n is
    a (finite) integer greater than one, in order to show it correct
    we need to perform induction not structurally on dynamic
    evaluation but on the number of times dynamic evaluation enters a
    loop body. *)

Reserved Notation "c1 '/' st '||' st' '#' n"
  (at level 40, st at level 39, st' at level 39).

Inductive ceval_count : com -> state -> state -> nat -> Prop :=
  | E'Skip : forall st,
      SKIP / st || st # 0
  | E'Ass  : forall st a1 n l,
      aeval st a1 = n ->
      (l ::= a1) / st || (update st l n) # 0
  | E'Seq : forall c1 c2 st st' st'' n1 n2,
      c1 / st  || st'  # n1 ->
      c2 / st' || st'' # n2 ->
      (c1 ; c2) / st || st'' # (n1 + n2)
  | E'IfTrue : forall st st' b1 c1 c2 n,
      beval st b1 = true ->
      c1 / st || st' # n ->
      (IFB b1 THEN c1 ELSE c2 FI) / st || st' # n
  | E'IfFalse : forall st st' b1 c1 c2 n,
      beval st b1 = false ->
      c2 / st || st' # n ->
      (IFB b1 THEN c1 ELSE c2 FI) / st || st' # n
  | E'WhileEnd : forall b1 st c1,
      beval st b1 = false ->
      (WHILE b1 DO c1 END) / st || st # 0
  | E'WhileLoop : forall st st' st'' b1 c1 n1 n2,
      beval st b1 = true ->
      c1 / st || st' # n1 ->
      (WHILE b1 DO c1 END) / st' || st'' # n2 ->
      (WHILE b1 DO c1 END) / st || st'' # S (n1 + n2)

  where "c1 '/' st '||' st' # n" := (ceval_count c1 st st' n).

Tactic Notation "ceval_count_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "E'Skip" | Case_aux c "E'Ass" | Case_aux c "E'Seq"
  | Case_aux c "E'IfTrue" | Case_aux c "E'IfFalse"
  | Case_aux c "E'WhileEnd" | Case_aux c "E'WhileLoop" ].

Hint Constructors ceval_count.

Theorem ceval_count_complete: forall c st st',
  c / st || st' -> exists n, c / st || st' # n.
Proof. intros c st st' Heval.
  induction Heval;
    try inversion IHHeval1;
    try inversion IHHeval2;
    try inversion IHHeval;
    eauto. Qed.

Theorem ceval_count_sound: forall c st st' n,
  c / st || st' # n -> c / st || st'.
Proof. intros c st st' n Heval. induction Heval; eauto. Qed.

Theorem pe_compare_nil_lookup: forall pe_st1 pe_st2,
  pe_compare pe_st1 pe_st2 = [] ->
  forall V, pe_lookup pe_st1 V = pe_lookup pe_st2 V.
Proof. intros pe_st1 pe_st2 H V.
  apply (pe_compare_correct pe_st1 pe_st2 V).
  rewrite H. reflexivity. Qed.

Theorem pe_compare_nil_override: forall pe_st1 pe_st2,
  pe_compare pe_st1 pe_st2 = [] ->
  forall st, pe_override st pe_st1 = pe_override st pe_st2.
Proof. intros pe_st1 pe_st2 H st.
  apply functional_extensionality. intros V.
  rewrite !pe_override_correct.
  apply pe_compare_nil_lookup with (V:=V) in H.
  rewrite H. reflexivity. Qed.

Reserved Notation "c' '/' pe_st' '/' c'' '/' st '||' st'' '#' n"
  (at level 40, pe_st' at level 39, c'' at level 39,
   st at level 39, st'' at level 39).

Inductive pe_ceval_count (c':com) (pe_st':pe_state) (c'':com)
                         (st:state) (st'':state) (n:nat) : Prop :=
  | pe_ceval_count_intro : forall st' n',
    c' / st || st' ->
    c'' / pe_override st' pe_st' || st'' # n' ->
    n' <= n ->
    c' / pe_st' / c'' / st || st'' # n
  where "c' '/' pe_st' '/' c'' '/' st '||' st'' '#' n" :=
        (pe_ceval_count c' pe_st' c'' st st'' n).

Hint Constructors pe_ceval_count.

Lemma pe_ceval_count_le: forall c' pe_st' c'' st st'' n n',
  n' <= n ->
  c' / pe_st' / c'' / st || st'' # n' ->
  c' / pe_st' / c'' / st || st'' # n.
Proof. intros c' pe_st' c'' st st'' n n' Hle H. inversion H.
  econstructor; try eassumption. omega. Qed.

Theorem pe_com_complete:
  forall c pe_st pe_st' c' c'', c / pe_st || c' / pe_st' / c'' ->
  forall st st'' n,
  (c / pe_override st pe_st || st'' # n) ->
  (c' / pe_st' / c'' / st || st'' # n).
Proof. intros c pe_st pe_st' c' c'' Hpe.
  pe_com_cases (induction Hpe) Case; intros st st'' n Heval;
  try (inversion Heval; subst;
       try (rewrite -> pe_bexp_correct, -> H in *; solve by inversion);
       []);
  eauto.
  Case "PE_AssStatic". econstructor. econstructor.
    rewrite -> pe_aexp_correct. rewrite <- pe_override_update_add.
    rewrite -> H. apply E'Skip. auto.
  Case "PE_AssDynamic". econstructor. econstructor. reflexivity.
    rewrite -> pe_aexp_correct. rewrite <- pe_override_update_remove.
    apply E'Skip. auto.
  Case "PE_Seq".
    edestruct IHHpe1 as [? ? ? Hskip ?]. eassumption.
    inversion Hskip. subst.
    edestruct IHHpe2. eassumption.
    econstructor; eauto. omega.
  Case "PE_If". inversion Heval; subst.
    SCase "E'IfTrue". edestruct IHHpe1. eassumption.
      econstructor. apply E_IfTrue. rewrite <- pe_bexp_correct. assumption.
      eapply E_Seq. eassumption. apply eval_assign.
      rewrite <- assign_removes. eassumption. eassumption.
    SCase "E_IfFalse". edestruct IHHpe2. eassumption.
      econstructor. apply E_IfFalse. rewrite <- pe_bexp_correct. assumption.
      eapply E_Seq. eassumption. apply eval_assign.
      rewrite -> pe_compare_override.
      rewrite <- assign_removes. eassumption. eassumption.
  Case "PE_WhileLoop".
    edestruct IHHpe1 as [? ? ? Hskip ?]. eassumption.
    inversion Hskip. subst.
    edestruct IHHpe2. eassumption.
    econstructor; eauto. omega.
  Case "PE_While". inversion Heval; subst.
    SCase "E_WhileEnd". econstructor. apply E_IfFalse.
      rewrite <- pe_bexp_correct. assumption.
      apply eval_assign.
      rewrite <- assign_removes. inversion H2; subst; auto.
      auto.
    SCase "E_WhileLoop".
      edestruct IHHpe1 as [? ? ? Hskip ?]. eassumption.
      inversion Hskip. subst.
      edestruct IHHpe2. eassumption.
      econstructor. apply E_IfTrue.
      rewrite <- pe_bexp_correct. assumption.
      repeat eapply E_Seq; eauto. apply eval_assign.
      rewrite -> pe_compare_override, <- assign_removes. eassumption.
      omega.
  Case "PE_WhileFixedLoop". apply ex_falso_quodlibet.
    generalize dependent (S (n1 + n2)). intros n.
    clear - Case H H0 IHHpe1 IHHpe2. generalize dependent st.
    induction n using lt_wf_ind; intros st Heval. inversion Heval; subst.
    SCase "E'WhileEnd". rewrite pe_bexp_correct, H in H7. inversion H7.
    SCase "E'WhileLoop".
      edestruct IHHpe1 as [? ? ? Hskip ?]. eassumption.
      inversion Hskip. subst.
      edestruct IHHpe2. eassumption.
      rewrite <- (pe_compare_nil_override _ _ H0) in H7.
      apply H1 in H7; [| omega]. inversion H7.
  Case "PE_WhileFixed". generalize dependent st.
    induction n using lt_wf_ind; intros st Heval. inversion Heval; subst.
    SCase "E'WhileEnd". rewrite pe_bexp_correct in H8. eauto.
    SCase "E'WhileLoop". rewrite pe_bexp_correct in H5.
      edestruct IHHpe1 as [? ? ? Hskip ?]. eassumption.
      inversion Hskip. subst.
      edestruct IHHpe2. eassumption.
      rewrite <- (pe_compare_nil_override _ _ H1) in H8.
      apply H2 in H8; [| omega]. inversion H8.
      econstructor; [ eapply E_WhileLoop; eauto | eassumption | omega].
Qed.

Theorem pe_com_sound:
  forall c pe_st pe_st' c' c'', c / pe_st || c' / pe_st' / c'' ->
  forall st st'' n,
  (c' / pe_st' / c'' / st || st'' # n) ->
  (c / pe_override st pe_st || st'').
Proof. intros c pe_st pe_st' c' c'' Hpe.
  pe_com_cases (induction Hpe) Case;
    intros st st'' n [st' n' Heval Heval' Hle];
    try (inversion Heval; []; subst);
    try (inversion Heval'; []; subst); eauto.
  Case "PE_AssStatic". rewrite <- pe_override_update_add. apply E_Ass.
    rewrite -> pe_aexp_correct. rewrite -> H. reflexivity.
  Case "PE_AssDynamic". rewrite <- pe_override_update_remove. apply E_Ass.
    rewrite <- pe_aexp_correct. reflexivity.
  Case "PE_Seq". eapply E_Seq; eauto.
  Case "PE_IfTrue". apply E_IfTrue.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity.
    eapply IHHpe. eauto.
  Case "PE_IfFalse". apply E_IfFalse.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity.
    eapply IHHpe. eauto.
  Case "PE_If". inversion Heval; subst; inversion H7; subst; clear H7.
    SCase "E_IfTrue".
      eapply ceval_deterministic in H8; [| apply eval_assign]. subst.
      rewrite <- assign_removes in Heval'.
      apply E_IfTrue. rewrite -> pe_bexp_correct. assumption.
      eapply IHHpe1. eauto.
    SCase "E_IfFalse".
      eapply ceval_deterministic in H8; [| apply eval_assign]. subst.
      rewrite -> pe_compare_override in Heval'.
      rewrite <- assign_removes in Heval'.
      apply E_IfFalse. rewrite -> pe_bexp_correct. assumption.
      eapply IHHpe2. eauto.
  Case "PE_WhileEnd". apply E_WhileEnd.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity.
  Case "PE_WhileLoop". eapply E_WhileLoop.
    rewrite -> pe_bexp_correct. rewrite -> H. reflexivity.
    eapply IHHpe1. eauto. eapply IHHpe2. eauto.
  Case "PE_While". inversion Heval; subst.
    SCase "E_IfTrue".
      inversion H9. subst. clear H9.
      inversion H10. subst. clear H10.
      eapply ceval_deterministic in H11; [| apply eval_assign]. subst.
      rewrite -> pe_compare_override in Heval'.
      rewrite <- assign_removes in Heval'.
      eapply E_WhileLoop. rewrite -> pe_bexp_correct. assumption.
      eapply IHHpe1. eauto.
      eapply IHHpe2. eauto.
    SCase "E_IfFalse". apply ceval_count_sound in Heval'.
      eapply ceval_deterministic in H9; [| apply eval_assign]. subst.
      rewrite <- assign_removes in Heval'.
      inversion H2; subst.
      SSCase "c2'' = SKIP". inversion Heval'. subst. apply E_WhileEnd.
        rewrite -> pe_bexp_correct. assumption.
      SSCase "c2'' = WHILE b1 DO c1 END". assumption.
  Case "PE_WhileFixedEnd". eapply ceval_count_sound. apply Heval'.
  Case "PE_WhileFixedLoop".
    apply loop_never_stops in Heval. inversion Heval.
  Case "PE_WhileFixed".
    clear - Case H1 IHHpe1 IHHpe2 Heval.
    remember (WHILE pe_bexp pe_st b1 DO c1'; c2' END) as c'.
    ceval_cases (induction Heval) SCase;
      inversion Heqc'; subst; clear Heqc'.
    SCase "E_WhileEnd". apply E_WhileEnd.
      rewrite pe_bexp_correct. assumption.
    SCase "E_WhileLoop".
      assert (IHHeval2' := IHHeval2 (refl_equal _)).
      apply ceval_count_complete in IHHeval2'. inversion IHHeval2'.
      clear IHHeval1 IHHeval2 IHHeval2'.
      inversion Heval1. subst.
      eapply E_WhileLoop. rewrite pe_bexp_correct. assumption. eauto.
      eapply IHHpe2. econstructor. eassumption.
      rewrite <- (pe_compare_nil_override _ _ H1). eassumption. apply le_n.
Qed.

Corollary pe_com_correct:
  forall c pe_st pe_st' c', c / pe_st || c' / pe_st' / SKIP ->
  forall st st'',
  (c / pe_override st pe_st || st'') <->
  (exists st', c' / st || st' /\ pe_override st' pe_st' = st'').
Proof. intros c pe_st pe_st' c' H st st''. split.
  Case "->". intros Heval.
    apply ceval_count_complete in Heval. inversion Heval as [n Heval'].
    apply pe_com_complete with (st:=st) (st'':=st'') (n:=n) in H.
    inversion H as [? ? ? Hskip ?]. inversion Hskip. subst. eauto.
    assumption.
  Case "<-". intros [st' [Heval Heq]]. subst st''.
    eapply pe_com_sound in H. apply H.
    econstructor. apply Heval. apply E'Skip. apply le_n.
Qed.

End Loop.

(* ####################################################### *)
(** * Partial Evaluation of Flowchart Programs *)

(** Instead of partially evaluating [WHILE] loops directly, the
    standard approach to partially evaluating imperative programs is
    to convert them into _flowcharts_.  In other words, it turns out
    that adding labels and jumps to our language makes it much easier
    to partially evaluate.  The result of partially evaluating a
    flowchart is a residual flowchart.  If we are lucky, the jumps in
    the residual flowchart can be converted back to [WHILE] loops, but
    that is not possible in general; we do not pursue it here. *)

(** ** Basic blocks *)

(** A flowchart is made of _basic blocks_, which we represent with the
    inductive type [block].  A basic block is a sequence of
    assignments (the constructor [Assign]), concluding with a
    conditional jump (the constructor [If]) or an unconditional jump
    (the constructor [Goto]).  The destinations of the jumps are
    specified by _labels_, which can be of any type.  Therefore, we
    parameterize the [block] type by the type of labels. *)

Inductive block (Label:Type) : Type :=
  | Goto : Label -> block Label
  | If : bexp -> Label -> Label -> block Label
  | Assign : id -> aexp -> block Label -> block Label.

Tactic Notation "block_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "Goto" | Case_aux c "If" | Case_aux c "Assign" ].

Implicit Arguments Goto   [[Label]].
Implicit Arguments If     [[Label]].
Implicit Arguments Assign [[Label]].

(** We use the "even or odd" program, expressed above in Imp, as our
    running example.  Converting this program into a flowchart turns
    out to require 4 labels, so we define the following type. *)

Inductive parity_label : Type :=
  | entry : parity_label
  | loop  : parity_label
  | body  : parity_label
  | done  : parity_label.

(** The following [block] is the basic block found at the [body] label
    of the example program. *)

Definition parity_body : block parity_label :=
  Assign Y (AMinus (AId Y) (ANum 1))
   (Assign X (AMinus (ANum 1) (AId X))
     (Goto loop)).

(** To evaluate a basic block, given an initial state, is to compute
    the final state and the label to jump to next.  Because basic
    blocks do not _contain_ loops or other control structures,
    evaluation of basic blocks is a total function -- we don't need to
    worry about non-termination. *)

Fixpoint keval {L:Type} (st:state) (k : block L) : state * L :=
  match k with
  | Goto l => (st, l)
  | If b l1 l2 => (st, if beval st b then l1 else l2)
  | Assign i a k => keval (update st i (aeval st a)) k
  end.

Example keval_example:
  keval empty_state parity_body
  = (update (update empty_state Y 0) X 1, loop).
Proof. reflexivity. Qed.

(** ** Flowchart programs *)

(** A flowchart program is simply a lookup function that maps labels
    to basic blocks.  Actually, some labels are _halting states_ and
    do not map to any basic block.  So, more precisely, a flowchart
    [program] whose labels are of type [L] is a function from [L] to
    [option (block L)]. *)

Definition program (L:Type) : Type := L -> option (block L).

Definition parity : program parity_label := fun l =>
  match l with
  | entry => Some (Assign X (ANum 0) (Goto loop))
  | loop => Some (If (BLe (ANum 1) (AId Y)) body done)
  | body => Some parity_body
  | done => None (* halt *)
  end.

(** Unlike a basic block, a program may not terminate, so we model the
    evaluation of programs by an inductive relation [peval] rather
    than a recursive function. *)

Inductive peval {L:Type} (p : program L)
  : state -> L -> state -> L -> Prop :=
  | E_None: forall st l,
    p l = None ->
    peval p st l st l
  | E_Some: forall st l k st' l' st'' l'',
    p l = Some k ->
    keval st k = (st', l') ->
    peval p st' l' st'' l'' ->
    peval p st l st'' l''.

Example parity_eval: peval parity empty_state entry empty_state done.
Proof. erewrite f_equal with (f := fun st => peval _ _ _ st _).
  eapply E_Some. reflexivity. reflexivity.
  eapply E_Some. reflexivity. reflexivity.
  apply E_None. reflexivity.
  apply functional_extensionality. intros i. rewrite update_same; auto.
Qed.

Tactic Notation "peval_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "E_None" | Case_aux c "E_Some" ].

(** ** Partial evaluation of basic blocks and flowchart programs *)

(** Partial evaluation changes the label type in a systematic way: if
    the label type used to be [L], it becomes [pe_state * L].  So the
    same label in the original program may be unfolded, or blown up,
    into multiple labels by being paired with different partial
    states.  For example, the label [loop] in the [parity] program
    will become two labels: [([(X,0)], loop)] and [([(X,1)], loop)].
    This change of label type is reflected in the types of [pe_block]
    and [pe_program] defined presently. *)

Fixpoint pe_block {L:Type} (pe_st:pe_state) (k : block L)
  : block (pe_state * L) :=
  match k with
  | Goto l => Goto (pe_st, l)
  | If b l1 l2 =>
    match pe_bexp pe_st b with
    | BTrue  => Goto (pe_st, l1)
    | BFalse => Goto (pe_st, l2)
    | b'     => If b' (pe_st, l1) (pe_st, l2)
    end
  | Assign i a k =>
    match pe_aexp pe_st a with
    | ANum n => pe_block (pe_add pe_st i n) k
    | a' => Assign i a' (pe_block (pe_remove pe_st i) k)
    end
  end.

Example pe_block_example:
  pe_block [(X,0)] parity_body
  = Assign Y (AMinus (AId Y) (ANum 1)) (Goto ([(X,1)], loop)).
Proof. reflexivity. Qed.

Theorem pe_block_correct: forall (L:Type) st pe_st k st' pe_st' (l':L),
  keval st (pe_block pe_st k) = (st', (pe_st', l')) ->
  keval (pe_override st pe_st) k = (pe_override st' pe_st', l').
Proof. intros. generalize dependent pe_st. generalize dependent st.
  block_cases (induction k as [l | b l1 l2 | i a k]) Case;
    intros st pe_st H.
  Case "Goto". inversion H; reflexivity.
  Case "If".
    replace (keval st (pe_block pe_st (If b l1 l2)))
       with (keval st (If (pe_bexp pe_st b) (pe_st, l1) (pe_st, l2)))
       in H by (simpl; destruct (pe_bexp pe_st b); reflexivity).
    simpl in *. rewrite pe_bexp_correct.
    destruct (beval st (pe_bexp pe_st b)); inversion H; reflexivity.
  Case "Assign".
    simpl in *. rewrite pe_aexp_correct.
    destruct (pe_aexp pe_st a); simpl;
      try solve [rewrite pe_override_update_add; apply IHk; apply H];
      solve [rewrite pe_override_update_remove; apply IHk; apply H].
Qed.

Definition pe_program {L:Type} (p : program L)
  : program (pe_state * L) :=
  fun pe_l => match pe_l with (pe_st, l) =>
                option_map (pe_block pe_st) (p l)
              end.

Inductive pe_peval {L:Type} (p : program L)
  (st:state) (pe_st:pe_state) (l:L) (st'o:state) (l':L) : Prop :=
  | pe_peval_intro : forall st' pe_st',
    peval (pe_program p) st (pe_st, l) st' (pe_st', l') ->
    pe_override st' pe_st' = st'o ->
    pe_peval p st pe_st l st'o l'.

Theorem pe_program_correct:
  forall (L:Type) (p : program L) st pe_st l st'o l',
  peval p (pe_override st pe_st) l st'o l' <->
  pe_peval p st pe_st l st'o l'.
Proof. intros.
  split; [Case "->" | Case "<-"].
  Case "->". intros Heval.
    remember (pe_override st pe_st) as sto.
    generalize dependent pe_st. generalize dependent st.
    peval_cases (induction Heval as
      [ sto l Hlookup | sto l k st'o l' st''o l'' Hlookup Hkeval Heval ])
      SCase; intros st pe_st Heqsto; subst sto.
    SCase "E_None". eapply pe_peval_intro. apply E_None.
      simpl. rewrite Hlookup. reflexivity. reflexivity.
    SCase "E_Some".
      remember (keval st (pe_block pe_st k)) as x.
      destruct x as [st' [pe_st' l'_]].
      symmetry in Heqx. erewrite pe_block_correct in Hkeval by apply Heqx.
      inversion Hkeval. subst st'o l'_. clear Hkeval.
      edestruct IHHeval. reflexivity. subst st''o. clear IHHeval.
      eapply pe_peval_intro; [| reflexivity]. eapply E_Some; eauto.
      simpl. rewrite Hlookup. reflexivity.
  Case "<-". intros [st' pe_st' Heval Heqst'o].
    remember (pe_st, l) as pe_st_l.
    remember (pe_st', l') as pe_st'_l'.
    generalize dependent pe_st. generalize dependent l.
    peval_cases (induction Heval as
      [ st [pe_st_ l_] Hlookup
      | st [pe_st_ l_] pe_k st' [pe_st'_ l'_] st'' [pe_st'' l'']
        Hlookup Hkeval Heval ])
      SCase; intros l pe_st Heqpe_st_l;
      inversion Heqpe_st_l; inversion Heqpe_st'_l'; repeat subst.
    SCase "E_None". apply E_None. simpl in Hlookup.
      destruct (p l'); [ solve [ inversion Hlookup ] | reflexivity ].
    SCase "E_Some".
      simpl in Hlookup. remember (p l) as k.
      destruct k as [k|]; inversion Hlookup; subst.
      eapply E_Some; eauto. apply pe_block_correct. apply Hkeval.
Qed.


From fae_gtlc_mu.refinements.static_gradual Require Export logical_relation.
From fae_gtlc_mu.backtranslation Require Export alternative_consistency.
From fae_gtlc_mu.backtranslation Require Export cast_help.general_def.
From fae_gtlc_mu.cast_calculus Require Export lang.

(* This file defines what needs to be proven for the compatibility lemma for casts. *)

Section defs.
  Context `{!implG Σ,!specG Σ}.

  (* Defines relatedness for a list of static values with respect A, a list of pairs of gradual types. *)
  (* This definition corresponds to equation 5 in the paper. *)

  Definition rel_cast_functions A (fs : list stlc_mu.lang.val) : iProp Σ :=
    ⌜length A = length fs⌝ ∗
    [∗ list] p ; f ∈ A ; fs , (
                           □ (∀ (v : stlc_mu.lang.val) (v' : cast_calculus.lang.val) ,
                                 ⟦ p.1 ⟧ (v , v') → ⟦ p.2 ⟧ₑ ((stlc_mu.lang.of_val f v) , Cast (v') p.1 p.2))
                         )%I.

  Global Instance rel_cast_functions_persistent A fs :
    Persistent (rel_cast_functions A fs).
  Proof.
    apply bi.sep_persistent; first by apply bi.pure_persistent.
    apply big_sepL2_persistent. intros _ (τi , τf) f. simpl.
    apply bi.intuitionistically_persistent.
  Qed.

  (** The (to-be-proven) statement that the -- closed up -- back-translated casts behave appropriately.
      This corresponds to the statement of lemma 4.7 in the paper;
      it's slightly adjust though to facilitate its proof. *)

  Definition back_cast_ar {A} {τi τf} (pC : alternative_consistency A τi τf) :=
  ∀ ei' K' v v' fs,
    ( rel_cast_functions A fs ∧ (* fs a list of values that relates to A as described above *)
      ⟦ τi ⟧ (v, v') ∧
      initially_inv ei' ∧
      currently_half (fill K' (Cast (cast_calculus.lang.of_val v') τi τf))
    )
      ⊢ (WP
           (𝓕c pC fs)(* the backtranslated cast; a function of type <<τi>> → <<τf>> *) v
           {{ w, ∃ w', currently_half (fill K' (cast_calculus.lang.of_val w')) ∧ ⟦ τf ⟧ (w, w') }}
        )%I.

End defs.


Require Import
        Coq.Strings.String
        Coq.Vectors.Vector.

Require Import
        Fiat.Computation
        Fiat.BinEncoders.Env.BinLib.CharString
        Fiat.BinEncoders.Env.Common.Specs
        Fiat.BinEncoders.Env.Common.WordFacts
        Fiat.BinEncoders.Env.Common.ComposeCheckSum
        Fiat.BinEncoders.Env.Common.ComposeIf
        Fiat.BinEncoders.Env.Common.ComposeOpt
        Fiat.BinEncoders.Env.Automation.Solver
        Fiat.BinEncoders.Env.Lib2.CInt
        Fiat.BinEncoders.Env.Lib2.NatOpt
        Fiat.BinEncoders.Env.Lib2.FixListOpt
        compcert.lib.Integers.

Module IntEncoder := CInt.Make(Wordsize_32).

Import IntEncoder.

Instance ByteStringQueueTransformer : Transformer ByteString := ByteStringQueueTransformer.

Definition Simple_Format_Spec
           (p : int * list int) :=
        encode_nat_Spec 8 (|snd p|)
  ThenC encode_int_Spec Int.wordsize (fst p)
  ThenC encode_list_Spec (encode_int_Spec Int.wordsize) (snd p)
  DoneC.

Definition Simply_OK (p : int * list int) :=
  ((|snd p|) < pow2 8)%nat.

Definition Simple_Format_decoder
  : CorrectDecoderFor Simply_OK Simple_Format_Spec.
Proof.
  start_synthesizing_decoder.
  normalize_compose transformer.
  repeat first [apply innt_decode_correct; auto
               | solve [ intros; apply intrange]
               | intros; simpl; match goal with |- _ /\ _ => split; intuition eauto end
               | decode_step idtac].
  synthesize_cache_invariant.
  cbv beta; optimize_decoder_impl.
Defined.

Definition Simple_Format_decoder_impl :=
  Eval simpl in (fst (projT1 Simple_Format_decoder)).

Print Simple_Format_decoder_impl.

Lemma foo {C} {numBytes}
  : forall (v : Vector.t byte (S numBytes)) (k : int -> ByteString -> unit -> option (C * ByteString * unit)),
    (`(a, b0, d) <- decode_int
      (transformerUnit := ByteString_QueueTransformerOpt) 8 (build_aligned_ByteString v) tt;
       k a b0 d) =
    Let n := byteToInt32 (Vector.nth v Fin.F1) in
    k n (build_aligned_ByteString (Vector.tl v)) ().
Proof.
  unfold LetIn; intros.
  unfold decode_int, WordOpt.decode_word.
  rewrite aligned_decode_char_eq; simpl.
  f_equal.
  pattern numBytes, v; apply Vector.caseS; simpl; intros.
  destruct h.
  destruct intval0; simpl; intros; intuition eauto.
  - compute; apply mkint_eq; reflexivity.
  - repeat first [compute in intrange0; destruct intrange0; congruence
                 | destruct p as [p | p | ]; simpl
                 | compute; apply mkint_eq; reflexivity].
  - compute in intrange0; destruct p; destruct intrange0; congruence.
Qed.

Lemma foo2 {C} {numBytes}
  : forall (v : Vector.t byte (S numBytes)) (k : nat -> ByteString -> unit -> option (C * ByteString * unit)),
    (`(a, b0, d) <- decode_nat
      (transformerUnit := ByteString_QueueTransformerOpt) 8 (build_aligned_ByteString v) tt;
       k a b0 d) =
    Let n := BinInt.Z.to_nat (Byte.intval (Vector.nth v Fin.F1)) in
    k n (build_aligned_ByteString (Vector.tl v)) ().
Proof.
  intros.
  unfold decode_nat, WordOpt.decode_word.
  rewrite aligned_decode_char_eq; simpl.
  f_equal.
  pattern numBytes, v; apply Vector.caseS; simpl; intros.
  destruct h.
  destruct intval0; simpl; intros; intuition eauto.
  - repeat first [compute in intrange0; destruct intrange0; congruence
                 | destruct p as [p | p | ]; simpl
                 | compute; reflexivity].
Qed.

Lemma foo3 {C} {numBytes}
  : forall (v : Vector.t byte (S (S (S (S numBytes))))) (k : int -> ByteString -> unit -> option (C * ByteString * unit)),
    (`(a, b0, d) <- decode_int
      (transformerUnit := ByteString_QueueTransformerOpt) Int.wordsize (build_aligned_ByteString v) tt;
       k a b0 d) =
    Let n := wordToInt (Core.append_word (byteToWord (Vector.nth v (Fin.FS (Fin.FS (Fin.FS Fin.F1)))))
                                         (Core.append_word (byteToWord (Vector.nth v (Fin.FS (Fin.FS Fin.F1))))
                                            (Core.append_word (byteToWord (Vector.nth v (Fin.FS Fin.F1)))
                                                              (byteToWord (Vector.nth v Fin.F1)))))
    in
    k n (build_aligned_ByteString (Vector.tl (Vector.tl (Vector.tl (Vector.tl v))))) ().
Proof.
  unfold LetIn; intros.
  unfold decode_int, WordOpt.decode_word.
  unfold Int.wordsize, Wordsize_32.wordsize.
  rewrite DecodeBindOpt2_assoc.
  pose proof (@aligned_decode_char_eq' _ 3 v).
  match goal with
    |- context[Ifopt ?Z as _ Then _ Else _] => replace Z with
                                               (let (v', v'') := Vector_split 4 numBytes v in Some (VectorByteToWord v', build_aligned_ByteString v'')) by (rewrite <- H; reflexivity)
  end.
  unfold Vector_split, If_Opt_Then_Else, DecodeBindOpt2 at 1, If_Opt_Then_Else.
  unfold DecodeBindOpt2 at 1, If_Opt_Then_Else.
  repeat f_equal.
  rewrite Core.append_word_assoc; erewrite VectorByteToWord_cons; repeat f_equal.
  rewrite Core.append_word_assoc, <- !Eqdep_dec.eq_rect_eq_dec; eauto using Peano_dec.eq_nat_dec.
  f_equal.
  erewrite VectorByteToWord_cons; f_equal.
  rewrite <- !Eqdep_dec.eq_rect_eq_dec; eauto using Peano_dec.eq_nat_dec; f_equal.
  erewrite VectorByteToWord_cons; f_equal.
  rewrite <- !Eqdep_dec.eq_rect_eq_dec; eauto using Peano_dec.eq_nat_dec; f_equal.
  erewrite VectorByteToWord_cons; f_equal.
  rewrite <- !Eqdep_dec.eq_rect_eq_dec; eauto using Peano_dec.eq_nat_dec; f_equal.
  simpl; f_equal; rewrite !Vector_nth_tl, Vector_nth_hd; reflexivity.
  simpl; f_equal; rewrite !Vector_nth_tl, Vector_nth_hd; reflexivity.
  simpl; f_equal; rewrite !Vector_nth_tl, Vector_nth_hd; reflexivity.
  simpl; f_equal; rewrite Vector_nth_hd; reflexivity.
  Grab Existential Variables.
  omega.
  omega.
  omega.
Qed.

Definition ofbytes (hi himid hilo lo: byte) : int :=
  or (shl (repr (Byte.unsigned hi)) (repr 24))
     (or (shl (repr (Byte.unsigned himid)) (repr 16))
         (or (shl (repr (Byte.unsigned hilo)) (repr 8))
             (repr (Byte.unsigned lo)))).

Lemma wordToInt_ofbytes_eq
  : forall (hi himid hilo lo: byte),
    wordToInt (Core.append_word
                 (byteToWord lo)
                 (Core.append_word (byteToWord hilo) (Core.append_word (byteToWord himid)
                                                                       (byteToWord hi)))) =
    ofbytes hi himid hilo lo.
Proof.
Admitted.

Lemma foo4 {C} {numBytes}
  : forall (v : Vector.t byte (S (S (S (S numBytes))))) (k : int -> ByteString -> unit -> option (C * ByteString * unit)),
    (`(a, b0, d) <- decode_int
      (transformerUnit := ByteString_QueueTransformerOpt) Int.wordsize (build_aligned_ByteString v) tt;
       k a b0 d) =
    Let n := ofbytes (Vector.nth v Fin.F1)
                     (Vector.nth v (Fin.FS Fin.F1))
                     (Vector.nth v (Fin.FS (Fin.FS Fin.F1)))
                     (Vector.nth v (Fin.FS (Fin.FS (Fin.FS Fin.F1))))
    in
    k n (build_aligned_ByteString (Vector.tl (Vector.tl (Vector.tl (Vector.tl v))))) ().
Proof.
  intros; rewrite foo3.
  rewrite wordToInt_ofbytes_eq.
  reflexivity.
Qed.

Definition aligned_Simple_Format_decoder_impl (bs : Vector.t byte 32) :
  {n : option (int * list int * ByteString * ()) & n =
                                                        Simple_Format_decoder_impl (build_aligned_ByteString bs) ()}.
  eexists.
  symmetry; unfold Simple_Format_decoder_impl.
  erewrite (foo2 bs).
  apply f_equal; apply Coq.Logic.FunctionalExtensionality.functional_extensionality; intro.
  rewrite foo4.
  etransitivity.
  apply f_equal; apply Coq.Logic.FunctionalExtensionality.functional_extensionality; intro.
Abort.


(* Copyright © 1998-2006
 * Henk Barendregt
 * Luís Cruz-Filipe
 * Herman Geuvers
 * Mariusz Giero
 * Rik van Ginneken
 * Dimitri Hendriks
 * Sébastien Hinderer
 * Bart Kirkels
 * Pierre Letouzey
 * Iris Loeb
 * Lionel Mamane
 * Milad Niqui
 * Russell O’Connor
 * Randy Pollack
 * Nickolay V. Shmyrev
 * Bas Spitters
 * Dan Synek
 * Freek Wiedijk
 * Jan Zwanenburg
 *
 * This work is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This work is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this work; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *)

Require Export CoRN.ftc.TaylorLemma.

Opaque Min Max N_Deriv.

Section More_Taylor_Defs.

(**
** General case

The generalization to arbitrary intervals just needs a few more definitions.

%\begin{convention}% Let [I] be a proper interval, [F:PartIR] and
[a,b:IR] be points of [I].
%\end{convention}%
*)

Variable I : interval.
Hypothesis pI : proper I.

Variable F : PartIR.

(* begin show *)
Let deriv_Sn b n Hf :=
 N_Deriv _ pI (S n) F Hf{*} [-C-] ([1][/] _[//]nring_fac_ap_zero _ n) {*} ( [-C-]b{-}FId) {^}n.
(* end show *)

Variables a b : IR.
Hypothesis Ha : I a.
Hypothesis Hb : I b.

(* begin show *)
Let fi n Hf i Hi :=
  N_Deriv _ pI _ _ (le_imp_Diffble_n _ _ _ _ (lt_n_Sm_le i n Hi) F Hf).

Let funct_i n Hf i Hi :=
  [-C-] (fi n Hf i Hi a Ha[/] _[//]nring_fac_ap_zero _ i) {*} (FId{-} [-C-]a) {^}i.
(* end show *)

Definition Taylor_Seq' n Hf := FSumx _ (funct_i n Hf).

(* begin hide *)
Lemma TaylorB : forall n Hf, Dom (Taylor_Seq' n Hf) b.
Proof.
 repeat split.
 apply FSumx_pred'; repeat split.
Qed.
(* end hide *)

Definition Taylor_Rem n Hf := F b (Diffble_n_imp_inc _ _ _ _ Hf b Hb) [-]
 Taylor_Seq' n Hf b (TaylorB n Hf).

(* begin hide *)
Lemma Taylor_Sumx_lemma : forall n x z y y', (forall H, y 0 H [=] z) ->
  (forall i H H', y' i H' [=] y (S i) H) ->
  x[-]Sumx (G:=IR) (n:=S n) y [=] x[-]z[-]Sumx (G:=IR) (n:=n) y'.
Proof.
 intro; induction  n as [| n Hrecn].
  intros; simpl in |- *.
  astepl (x[-] ([0][+]z)).
  rational.
 intros.
 astepl (x[-] (Sumx (fun i (l : i < S n) => y i (lt_S _ _ l)) [+] y (S n) (lt_n_Sn (S n)))).
 rstepl (x[-]Sumx (fun i (l : i < S n) => y i (lt_S _ _ l)) [-] y (S n) (lt_n_Sn (S n))).
 astepr (x[-]z[-] (Sumx (fun i (l : i < n) => y' i (lt_S _ _ l)) [+]y' n (lt_n_Sn n))).
 rstepr (x[-]z[-]Sumx (fun i (l : i < n) => y' i (lt_S _ _ l)) [-] y' n (lt_n_Sn n)).
 algebra.
Qed.

Lemma Taylor_lemma_ap : forall n Hf Hf' Ha',
 Taylor_Rem n Hf'[-]deriv_Sn b n Hf a Ha'[*] (b[-]a) [#] [0] -> a [#] b.
Proof.
 intros. rename X into H.
 set (Hpred := Diffble_n_imp_inc _ _ _ _ Hf') in *.
 cut (Taylor_Rem n Hf'[-]Part _ _ Ha'[*] (b[-]a) [#] [0][-][0]).
  2: astepr ZeroR; auto.
 clear H; intros. rename X into H.
 elim (cg_minus_strext _ _ _ _ _ H); clear H; intro H.
  unfold Taylor_Rem, Taylor_Seq', funct_i in H.
  cut (Dom (FSumx n (fun i (Hi : i < n) => [-C-] (fi n Hf' (S i) (lt_n_S _ _ Hi) a Ha[/] _[//]
    nring_fac_ap_zero IR (S i)) {*} (FId{-} [-C-]a) {^}S i)) b).
   2: apply FSumx_pred'; repeat split.
  intro H0.
  cut (F b (Hpred b Hb) [-]F a (Hpred a Ha) [#] [0] or Part _ _ H0 [#] [0]). intro H1.
   elim H1; clear H H1; intro H.
    apply pfstrx with (Hx := Hpred a Ha) (Hy := Hpred b Hb).
    apply ap_symmetric_unfolded; apply zero_minus_apart; auto.
   cut (ext_fun_seq' (fun i (Hi : i < n) => [-C-]
     (fi n Hf' (S i) (lt_n_S _ _ Hi) a Ha[/] _[//]nring_fac_ap_zero IR (S i)) {*}
       (FId{-} [-C-]a) {^}S i)).
    2: red in |- *; repeat split.
   intro H1.
   cut (Sumx (fun i (Hi : i < n) => Part _ _ (FSumx_pred n _ H1 _ H0 i Hi)) [#]
     Sumx (fun i (Hi : i < n) => [0])). intro H2.
    2: eapply ap_wdl_unfolded.
     2: eapply ap_wdr_unfolded.
      2: apply H.
     2: apply eq_symmetric_unfolded; eapply eq_transitive_unfolded; [ apply sumx_const | algebra ].
    2: exact (FSumx_char _ _ _ _ H1).
   simpl in H2.
   cut (nat_less_n_fun (fun i (Hi : i < n) =>
     (fi n Hf' (S i) (lt_n_S _ _ Hi) a Ha[/] _[//]nring_fac_ap_zero IR (S i)) [*]
       (nexp IR i (b[+][--]a) [*] (b[+][--]a)))); intros.
    cut (nat_less_n_fun (fun i (Hi : i < n) => ([0]:IR))); intros.
     elim (Sumx_strext _ _ _ _ H3 H4 H2); clear H H0 H1 H2 H3 H4; intros N HN.
     elim HN; clear HN; intros HN H.
     cut (b[+][--]a [#] [0]). intro H3.
      2: eapply cring_mult_ap_zero_op; eapply cring_mult_ap_zero_op; apply H.
     apply ap_symmetric_unfolded; apply zero_minus_apart; auto.
    red in |- *; algebra.
   red in |- *; do 3 intro.
   rewrite H3; intros; unfold fi in |- *.
   apply mult_wdl.
   apply div_wd.
    2: algebra.
   apply Feq_imp_eq with I.
    apply Derivative_n_unique with pI (S j) F; apply N_Deriv_lemma.
   auto.
  apply cg_minus_strext.
  astepr ZeroR.
  apply ap_wdl_unfolded with (Part _ _ (Hpred b Hb) [-]Part _ _ (TaylorB n Hf')); auto.
  unfold Taylor_Seq', funct_i in |- *.
  cut (ext_fun_seq' (fun i Hi => [-C-] (fi n Hf' i Hi a Ha[/] _[//]nring_fac_ap_zero IR i) {*}
    (FId{-} [-C-]a) {^}i)). intro H1.
   apply eq_transitive_unfolded with (Part _ _ (Hpred b Hb) [-] Sumx (fun i Hi =>
     Part _ _ (FSumx_pred _ _ H1 b (TaylorB n Hf') i Hi))).
    apply cg_minus_wd.
     algebra.
    exact (FSumx_char _ _ _ _ H1).
   cut (ext_fun_seq' (fun i (Hi : i < n) => [-C-]
     (fi n Hf' (S i) (lt_n_S _ _ Hi) a Ha[/] _[//]nring_fac_ap_zero IR (S i)) {*}
       (FId{-} [-C-]a) {^}S i)). intro H2.
    apply eq_transitive_unfolded with (Part _ _ (Hpred b Hb) [-]Part _ _ (Hpred a Ha) [-] Sumx
      (fun i (Hi : i < n) => Part _ _ (FSumx_pred _ _ H2 b H0 i Hi))).
     2: apply cg_minus_wd.
      2: algebra.
     2: apply eq_symmetric_unfolded; exact (FSumx_char _ _ _ _ H2).
    apply Taylor_Sumx_lemma.
     intros; simpl in |- *.
     unfold fi in |- *.
     rstepr ((Part _ _ (Hpred a Ha) [/] [0][+][1][//]nring_fac_ap_zero IR 0) [*][1]).
     apply mult_wdl; apply div_wd.
      2: algebra.
     apply Feq_imp_eq with I.
      apply Derivative_n_unique with pI 0 F.
       apply N_Deriv_lemma.
      split; auto.
      split; auto.
      intros; simpl in |- *.
      apply Feq_reflexive; Included.
     auto.
    intros; simpl in |- *.
    apply mult_wdl; apply div_wd.
     2: algebra.
    unfold fi in |- *.
    apply Feq_imp_eq with I.
     apply Derivative_n_unique with pI (S i) F; apply N_Deriv_lemma; auto.
    auto.
   repeat split.
  repeat split.
 apply ap_symmetric_unfolded; apply zero_minus_apart.
 eapply cring_mult_ap_zero_op; apply H.
Qed.
(* end hide *)

Theorem Taylor' : forall n Hf Hf' e, [0] [<] e -> {c : IR | Compact (Min_leEq_Max a b) c |
 forall Hc, AbsIR (Taylor_Rem n Hf'[-]deriv_Sn b n Hf c Hc[*] (b[-]a)) [<=] e}.
Proof.
 intros. rename X into H.
 cut (Dom (deriv_Sn b n Hf) a). intro H0.
  2: repeat split.
  2: simpl in |- *; auto.
 elim (less_cotransitive_unfolded _ _ _ H (AbsIR (Taylor_Rem n Hf'[-]Part _ _ H0[*] (b[-]a)))).
  intros.
  cut (a [#] b). intro H1.
   clear a0 H0.
   cut (Diffble_I_n (ap_imp_Min_less_Max _ _ H1) (S n) F). intro H0.
    2: apply included_imp_Diffble_n with I pI; auto.
    cut (Diffble_I_n (ap_imp_Min_less_Max _ _ H1) n F). intro H2.
     2: apply le_imp_Diffble_I with (S n); auto.
    elim (Taylor_lemma a b H1 F (Diffble_n_imp_inc _ _ _ _ Hf b Hb) e H n H2 H0).
    intros c H3 H4.
    exists c; auto.
    intro.
    cut (Dom (n_deriv_I _ _ _ _ _ H0{*} [-C-] ([1][/] _[//]nring_fac_ap_zero _ n) {*}
      ( [-C-]b{-}FId) {^}n) c). intro H5.
     2: repeat split.
     2: apply n_deriv_inc; auto.
    eapply leEq_wdl.
     apply (H4 H5).
    unfold Taylor_rem, Taylor_Rem in |- *.
    apply AbsIR_wd; repeat apply cg_minus_wd.
      algebra.
     simpl in |- *.
     repeat first [ apply bin_op_wd_unfolded | apply mult_wd | apply div_wd
       | apply eq_reflexive_unfolded ].
      apply FSumx_wd; intros; simpl in |- *.
      apply mult_wdl.
      apply div_wd.
       2: algebra.
      apply eq_transitive_unfolded with (PartInt (ProjT1 (Diffble_I_n_imp_deriv_n _ _ _ _ _
        (le_imp_Diffble_I _ _ _ _ _ (lt_n_Sm_le _ _ (lt_S _ _ Hi)) _ H2)))
          a (compact_Min_lft _ _ (less_leEq _ _ _ (ap_imp_Min_less_Max _ _ H1)))).
       simpl in |- *; algebra.
      apply Feq_imp_eq with (Compact (less_leEq _ _ _ (ap_imp_Min_less_Max _ _ H1))).
       apply Derivative_I_n_unique with i F.
        apply projT2.
       unfold fi in |- *.
       elim (N_Deriv_lemma _ _ _ _ (le_imp_Diffble_n I pI i n (lt_n_Sm_le _ _ (lt_S _ _ Hi)) _ Hf'));
         intros incF0 H'.
       elim H'; intros Hinc derivF; clear H'.
       apply derivF.
       simpl in |- *; Included.
      apply compact_Min_lft.
     apply eq_transitive_unfolded with (PartInt (ProjT1 (Diffble_I_n_imp_deriv_n _ _ _ _ _
       (le_imp_Diffble_I _ _ _ _ _ (lt_n_Sm_le _ _ (lt_n_Sn n)) _ H2)))
         a (compact_Min_lft _ _ (less_leEq _ _ _ (ap_imp_Min_less_Max _ _ H1)))).
      simpl in |- *; algebra.
     apply Feq_imp_eq with (Compact (less_leEq _ _ _ (ap_imp_Min_less_Max _ _ H1))).
      apply Derivative_I_n_unique with n F.
       apply projT2.
      unfold fi in |- *.
      elim (N_Deriv_lemma _ _ _ _ (le_imp_Diffble_n I pI n n (lt_n_Sm_le _ _ (lt_n_Sn n)) _ Hf'));
        intros incF0 H'.
      elim H'; intros Hinc derivF; clear H'.
      apply derivF.
      simpl in |- *; Included.
     apply compact_Min_lft.
    simpl in |- *.
    repeat apply mult_wdl.
    apply Feq_imp_eq with (Compact (less_leEq _ _ _ (ap_imp_Min_less_Max _ _ H1))).
     apply Derivative_I_n_unique with (S n) F.
      apply Derivative_I_n_wdr with (n_deriv_I _ _ _ _ _ H0).
       apply Derivative_I_n_unique with n (n_deriv_I _ _ _ _ _
         (le_imp_Diffble_I _ _ _ _ _ (le_n_S _ _ (le_O_n n)) _ H0)).
        cut (forall HS HSn, Derivative_I_n (ap_imp_Min_less_Max _ _ H1) n
          (n_deriv_I _ _ (ap_imp_Min_less_Max _ _ H1) 1 F HS)
            (n_deriv_I _ _ (ap_imp_Min_less_Max _ _ H1) (S n) F HSn)); auto.
        cut (S n = n + 1); [ intro | rewrite plus_comm; auto ].
        rewrite H6.
        intros; apply n_deriv_plus.
       eapply Derivative_I_n_wdl.
        2: apply n_deriv_lemma.
       apply Derivative_I_unique with F.
        apply projT2.
       apply Derivative_I_wdl with (n_deriv_I _ _ _ _ _ (le_imp_Diffble_I _ _ _ _ _ (le_O_n _) F H0)).
        simpl in |- *.
        FEQ.
        apply (included_trans _ (Compact (less_leEq IR (Min a b) (Max a b) (ap_imp_Min_less_Max a b H1))) I); Included.
       apply n_Sn_deriv.
      apply n_deriv_lemma.
     elim (N_Deriv_lemma _ _ _ _ Hf); intros incF0 H'.
     elim H'; intros Hinc derivF; clear H'.
     apply derivF.
     simpl in |- *; Included.
    elim H5; clear H5; intros H6 H7.
    elim H6; clear H6; intros H5 H8.
    exact (n_deriv_inc' _ _ _ _ _ _ _ _ H5).
   Included.
  cut (Taylor_Rem n Hf'[-]Part _ _ H0[*] (b[-]a) [#] [0]).
   intro H1; exact (Taylor_lemma_ap _ _ _ _ H1).
  astepr ZeroR; apply AbsIR_cancel_ap_zero; apply Greater_imp_ap; auto.
 intro.
 exists a.
  apply compact_Min_lft.
 intro; eapply leEq_wdl.
  apply less_leEq; apply b0.
 apply AbsIR_wd; rational.
Qed.

End More_Taylor_Defs.

Section Taylor_Theorem.

(**
And finally the ``nice'' version, when we know the expression of the
derivatives of [F].

%\begin{convention}% Let [f] be the sequence of derivatives of [F] of
order up to [n] and [F'] be the nth-derivative of [F].
%\end{convention}%
*)

Variable I : interval.
Hypothesis pI : proper I.

Variable F : PartIR.

Variable n : nat.
Variable f : forall i : nat, i < S n -> PartIR.

Hypothesis goodF : ext_fun_seq f.
Hypothesis goodF' : ext_fun_seq' f.

Hypothesis derF : forall i Hi, Derivative_n i I pI F (f i Hi).

Variable F' : PartIR.
Hypothesis derF' : Derivative_n (S n) I pI F F'.

Variables a b : IR.
Hypothesis Ha : I a.
Hypothesis Hb : I b.

(* begin show *)
Let funct_i i Hi := let HX := (Derivative_n_imp_inc' _ _ _ _ _ (derF i Hi) a Ha) in
[-C-] (f i Hi a HX [/] _[//] nring_fac_ap_zero _ i) {*} (FId{-} [-C-]a) {^}i.

Definition Taylor_Seq := FSumx _ funct_i.

Let deriv_Sn := F'{*} [-C-] ([1][/] _[//]nring_fac_ap_zero _ n) {*} ( [-C-]b{-}FId) {^}n.
(* end show *)

Lemma Taylor_aux : Dom Taylor_Seq b.
Proof.
 repeat split.
 apply FSumx_pred'; repeat split.
Qed.

Theorem Taylor : forall e, [0] [<] e -> forall Hb', {c : IR | Compact (Min_leEq_Max a b) c |
 forall Hc, AbsIR (F b Hb'[-]Part _ _ Taylor_aux[-]deriv_Sn c Hc[*] (b[-]a)) [<=] e}.
Proof.
 intros e H Hb'.
 cut (Diffble_n (S n) I pI F).
  intro Hf.
  cut (Diffble_n n I pI F).
   intro Hf'.
   elim (Taylor' I pI F _ _ Ha Hb n Hf Hf' e H); intros c Hc' Hc.
   exists c.
    auto.
   intros.
   cut (Dom (N_Deriv _ _ _ _ Hf{*} [-C-] ([1][/] _[//]nring_fac_ap_zero IR n) {*}
     ( [-C-]b{-}FId) {^}n) c). intro H0.
    eapply leEq_wdl.
     apply (Hc H0).
    apply AbsIR_wd; simpl in |- *; repeat simple apply cg_minus_wd.
     2: repeat simple apply mult_wdl.
     unfold Taylor_Rem in |- *; simpl in |- *.
     apply cg_minus_wd.
      algebra.
     apply bin_op_wd_unfolded.
      apply Feq_imp_eq with (Compact (Min_leEq_Max a b)).
       apply FSumx_wd'.
       unfold funct_i in |- *; intros; simpl in |- *.
       apply Feq_mult.
        FEQ.
        simpl in |- *.
        apply div_wd.
         apply Feq_imp_eq with I.
          apply Derivative_n_unique with pI i F.
           apply N_Deriv_lemma.
          apply derF.
         auto.
        algebra.
       apply Feq_reflexive; repeat split.
      apply compact_Min_rht.
     apply mult_wdl.
     apply div_wd.
      2: algebra.
     apply Feq_imp_eq with I.
      apply Derivative_n_unique with pI n F.
       apply N_Deriv_lemma.
      apply derF.
     auto.
    apply Feq_imp_eq with I.
     apply Derivative_n_unique with pI (S n) F.
      apply N_Deriv_lemma.
     assumption.
    cut (included (Compact (Min_leEq_Max a b)) I); Included.
   repeat split.
   Transparent N_Deriv.
   simpl in |- *.
   cut (included (Compact (Min_leEq_Max a b)) I); Included.
  apply Derivative_n_imp_Diffble_n with (f n (lt_n_Sn n)).
  apply derF.
 apply Derivative_n_imp_Diffble_n with F'.
 assumption.
Qed.

End Taylor_Theorem.


(*
 This is the definition of formal syntax for Dan Grossman's Thesis,
  "SAFE PROGRAMMING AT THE C LEVEL OF ABSTRACTION".

 Type Safety

*)

Set Implicit Arguments.
Require Export Cyclone_Formal_Syntax Cyclone_Static_Semantics_Kinding_And_Context_Well_Formedness.
Require Export Cyclone_Dynamic_Semantics.
Require Export Cyclone_Classes Cyclone_Inductions Cyclone_LN_Tactics Cyclone_LN_Extra_Lemmas_And_Automation.
Require Export Cyclone_WFC_Lemmas.
Require Export Cyclone_WFU_Lemmas.
Require Export Cyclone_Context_Weakening_Proof.
Require Export Cyclone_Substitutions_Proof.
Require Export Cyclone_LN_Types_Lemmas.
Require Export Cyclone_Get_Lemmas.
Require Export Cyclone_Admit_Environment.
Require Export Cyclone_Canonical_Forms_Proof.
Close Scope list_scope.
Import LibEnvNotations.
Import LVPE.LibVarPathEnvNotations.

Definition stuck H s :=
  ~(exists v, s = retn v) /\
  ~(exists H' s', S H s H' s').
(* Need a reflexive transitive closure of S. *)

Theorem Preservation:
  forall h s,
    prog h s ->
    forall h' s',
      S h s h' s' ->
      prog h' s'.
Admitted.

Theorem Progress:
  forall h s,
    prog h s ->
    (exists v, s = retn v) \/
    (exists h' s', S h s h' s').
Admitted.


(************************************************************************)
(*  v      *   The Coq Proof Assistant  /  The Coq Development Team     *)
(* <O___,, *   INRIA - CNRS - LIX - LRI - PPS - Copyright 1999-2011     *)
(*   \VV/  **************************************************************)
(*    //   *      This file is distributed under the terms of the       *)
(*         *       GNU Lesser General Public License Version 2.1        *)
(************************************************************************)
(**************************************************************************)
(*                                                                        *)
(* Omega: a solver of quantifier-free problems in Presburger Arithmetic   *)
(*                                                                        *)
(* Pierre Crégut (CNET, Lannion, France)                                  *)
(*                                                                        *)
(**************************************************************************)

(* $Id: Omega.v 14641 2011-11-06 11:59:10Z herbelin $ *)

(* We do not require [ZArith] anymore, but only what's necessary for Omega *)
Require Export ZArith_base.
Require Export OmegaLemmas.
Require Export PreOmega.
Declare ML Module "omega_plugin".

Hint Resolve Zle_refl Zplus_comm Zplus_assoc Zmult_comm Zmult_assoc Zplus_0_l
  Zplus_0_r Zmult_1_l Zplus_opp_l Zplus_opp_r Zmult_plus_distr_l
  Zmult_plus_distr_r: zarith.

Require Export Zhints.

(*
(* The constant minus is required in coq_omega.ml *)
Require Minus.
*)

Hint Extern 10 (_ = _ :>nat) => abstract omega: zarith.
Hint Extern 10 (_ <= _) => abstract omega: zarith.
Hint Extern 10 (_ < _) => abstract omega: zarith.
Hint Extern 10 (_ >= _) => abstract omega: zarith.
Hint Extern 10 (_ > _) => abstract omega: zarith.

Hint Extern 10 (_ <> _ :>nat) => abstract omega: zarith.
Hint Extern 10 (~ _ <= _) => abstract omega: zarith.
Hint Extern 10 (~ _ < _) => abstract omega: zarith.
Hint Extern 10 (~ _ >= _) => abstract omega: zarith.
Hint Extern 10 (~ _ > _) => abstract omega: zarith.

Hint Extern 10 (_ = _ :>Z) => abstract omega: zarith.
Hint Extern 10 (_ <= _)%Z => abstract omega: zarith.
Hint Extern 10 (_ < _)%Z => abstract omega: zarith.
Hint Extern 10 (_ >= _)%Z => abstract omega: zarith.
Hint Extern 10 (_ > _)%Z => abstract omega: zarith.

Hint Extern 10 (_ <> _ :>Z) => abstract omega: zarith.
Hint Extern 10 (~ (_ <= _)%Z) => abstract omega: zarith.
Hint Extern 10 (~ (_ < _)%Z) => abstract omega: zarith.
Hint Extern 10 (~ (_ >= _)%Z) => abstract omega: zarith.
Hint Extern 10 (~ (_ > _)%Z) => abstract omega: zarith.

Hint Extern 10 False => abstract omega: zarith.


Require Import Coq.ZArith.ZArith. Local Open Scope Z_scope.
Require Import coqutil.Datatypes.List.

(* RISC-V Calling Convention from
   https://raw.githubusercontent.com/riscv-non-isa/riscv-elf-psabi-doc/a855ba3ef4/riscv-cc.adoc

| Name    | ABI Mnemonic | Meaning                | Preserved across calls?

| x0      | zero         | Zero                   | -- (Immutable)
| x1      | ra           | Return address         | No
| x2      | sp           | Stack pointer          | Yes
| x3      | gp           | Global pointer         | -- (Unallocatable)
| x4      | tp           | Thread pointer         | -- (Unallocatable)
| x5-x7   | t0-t2        | Temporary registers    | No
| x8-x9   | s0-s1        | Callee-saved registers | Yes
| x10-x17 | a0-a7        | Argument registers     | No
| x18-x27 | s2-s11       | Callee-saved registers | Yes
| x28-x31 | t3-t6        | Temporary registers    | No
*)

Module reg_class.
  Inductive t := neg | zero | ra | sp | gp | tp | temp | saved | arg | stack_slot.
  Scheme Equality for t.
  Definition eqb := t_beq.
  Local Open Scope bool_scope.

  Definition get(r: Z): t :=
    if r <? 0 then neg else
    if r =? 0 then zero else
    if r =? 1 then ra else
    if r =? 2 then sp else
    if r =? 3 then gp else
    if r =? 4 then tp else
    if (5 <=? r) && (r <=? 7) then temp else
    if (8 <=? r) && (r <=? 9) then saved else
    if (10 <=? r) && (r <=? 17) then arg else
    if (18 <=? r) && (r <=? 27) then saved else
    if (28 <=? r) && (r <=? 31) then temp else
    stack_slot.

  Definition all(class: t): list Z :=
    List.filter (fun r => eqb (get r) class) (List.unfoldn (Z.add 1) 32 0).

  Definition caller_saved(r: Z): Prop :=
    match get r with
    | ra | temp | arg => True
    | neg | zero | sp | gp | tp | saved | stack_slot => False
    end.
End reg_class.

Require Import riscv.Utility.RegisterNames.
Require Import coqutil.Tactics.destr coqutil.Tactics.Simp coqutil.Tactics.Tactics.
Require Import coqutil.Z.Lia.

Lemma arg_range_Forall: List.Forall (fun r => 10 <= r <= 17) (reg_class.all reg_class.arg).
Proof.
  unfold reg_class.all.
  eapply Forall_filter.
  intros *. intro E. destr (reg_class.get a); try discriminate E.
  unfold reg_class.get in E0. simp.
  destruct_one_match_hyp.
  + assumption.
  + destruct_one_match_hyp. 1: discriminate.
    destruct_one_match_hyp; discriminate.
Qed.

Lemma saved_range_Forall:
  List.Forall (fun r => r = 8 \/ r = 9 \/ 18 <= r <= 27) (reg_class.all reg_class.saved).
Proof.
  unfold reg_class.all.
  eapply Forall_filter.
  intros *. intro E. destr (reg_class.get a); try discriminate E.
  unfold reg_class.get in E0. simp.
  destruct_one_match_hyp.
  + blia.
  + destruct_one_match_hyp. 1: discriminate.
    destruct_one_match_hyp.
    * auto.
    * destruct_one_match_hyp; try discriminate.
Qed.

Lemma not_in_arg_regs: forall x n,
    (n <= 8)%nat ->
    x < RegisterNames.a0 \/ RegisterNames.a7 < x ->
    ~ List.In x (List.firstn n (reg_class.all reg_class.arg)).
Proof.
  intros x n B1 B2 C.
  pose proof arg_range_Forall as P.
  eapply List.Forall_firstn in P.
  eapply List.Forall_forall in P. 2: exact C.
  unfold a0, a7 in *. blia.
Qed.

Lemma sp_not_in_arg_regs: forall n,
    ~ List.In RegisterNames.sp (List.firstn n (reg_class.all reg_class.arg)).
Proof.
  intros n C.
  pose proof arg_range_Forall as P.
  eapply List.Forall_firstn in P.
  eapply List.Forall_forall in P. 2: exact C.
  unfold RegisterNames.sp in P. blia.
Qed.

Lemma ra_not_in_arg_regs: forall n,
    ~ List.In RegisterNames.ra (List.firstn n (reg_class.all reg_class.arg)).
Proof.
  intros n C.
  pose proof arg_range_Forall as P.
  eapply List.Forall_firstn in P.
  eapply List.Forall_forall in P. 2: exact C.
  unfold RegisterNames.ra in P. blia.
Qed.

(* TODO move *)
Lemma firstn_unfoldn{A: Type}(f: A -> A): forall n m start,
    (n <= m)%nat ->
    List.firstn n (List.unfoldn f m start) = List.unfoldn f n start.
Proof.
  induction n; intros.
  - reflexivity.
  - destruct m. 1: inversion H.
    cbn. f_equal. eapply IHn. eapply le_S_n. assumption.
Qed.

Lemma all_arg_regs_alt:
  List.firstn 8 (reg_class.all reg_class.arg) = List.unfoldn (Z.add 1) 8 a0.
Proof. reflexivity. Qed.

Lemma arg_regs_alt: forall n,
    (n <= 8)%nat ->
    List.firstn n (reg_class.all reg_class.arg) = List.unfoldn (Z.add 1) n a0.
Proof.
  intros. erewrite <- firstn_unfoldn by eassumption. reflexivity.
Qed.