Module BTL_AnalysisLib


Require Import ZArith List Maps Coqlib Lattice Smallstep.
Require Import AST BTL Registers Op.
Require Import Linking IPass Values Integers Memory Events Globalenvs.
Require Import AnalysisDomain.
Require RTL ValueDomain.
Require Kildall MultiFixpoint.

Section BINARY_TREE.

Inductive binary_tree (A : Type) :=
| binary_tree_leaf : A -> binary_tree A
| binary_tree_split : binary_tree A -> binary_tree A -> binary_tree A.
Global Arguments binary_tree_leaf {A}.
Global Arguments binary_tree_split {A}.

Fixpoint binary_tree_fold {A B : Type} (f : A -> B -> B)
         (tr : binary_tree A) (b0 : B) :=
  match tr with
  | binary_tree_leaf a => f a b0
  | binary_tree_split l r =>
      binary_tree_fold f l (binary_tree_fold f r b0)
  end.

Fixpoint apply_at_leaves {A B : Type} (f : A -> binary_tree B)
         (tree : binary_tree A) :=
  match tree with
  | binary_tree_leaf a => f a
  | binary_tree_split l r =>
    binary_tree_split (apply_at_leaves f l) (apply_at_leaves f r)
  end.

Fixpoint match_tree {A : Type} (P : A -> Prop) (tr : binary_tree A) :=
  match tr with
  | binary_tree_leaf l => P l
  | binary_tree_split l r =>
      match_tree P l \/ match_tree P r
  end.

Fixpoint flatten_outcomes_rec
           { T : Type} (t : binary_tree (list T)) (already : list T) : list T :=
  match t with
  | binary_tree_leaf l => List.rev_append l already
  | binary_tree_split l r =>
      flatten_outcomes_rec l (flatten_outcomes_rec r already)
  end.

Lemma In_rev_append:
  forall {T} l1 l2 x, In (x : T) (List.rev_append l1 l2) <-> In x l1 \/ In x l2.
Proof.
  induction l1; intros; cbn.
  tauto.
  split; intro IN.
  { apply IHl1 in IN.
    cbn in IN.
    tauto.
  }
  pose proof (IHl1 (a :: l2) x) as Z.
  cbn in Z.
  tauto.
Qed.

Lemma apply_match_tree:
  forall {A B : Type} (P : A -> Prop) (Q : B -> Prop) (f : A -> binary_tree B)
         (MAP : forall x, P x -> match_tree Q (f x))
         tr
         (EX : match_tree P tr),
    match_tree Q (apply_at_leaves f tr).
Proof.
  induction tr; cbn; intros; intuition.
Qed.
    
Lemma In_binary_tree_flatten_outcomes_rec :
  forall { T : Type }
         (t : binary_tree (list T))
         (already : list T)
         (x : T),
      In x (flatten_outcomes_rec t already) <->
         match_tree (In x) t \/ In x already.
Proof.
  induction t; cbn; intros.
  { apply In_rev_append. }
  rewrite IHt1.
  rewrite IHt2.
  tauto.
Qed.

Definition flatten_outcomes
           { T : Type} (t : binary_tree (list T)) :=
  flatten_outcomes_rec t nil.

Lemma In_binary_tree_flatten_outcomes :
  forall { T : Type }
         (t : binary_tree (list T))
         (x : T),
      In x (flatten_outcomes t) <->
         match_tree (In x) t.
Proof.
  intros.
  unfold flatten_outcomes.
  rewrite In_binary_tree_flatten_outcomes_rec.
  cbn.
  tauto.
Qed.

Definition In_tree {T : Type} (l0 : T) : binary_tree T -> Prop :=
  match_tree (fun l => l = l0).

End BINARY_TREE.

Abstract Analysis

Module BTL_IRspec <: IRspec.
  Definition function := BTL.function.
  Definition node := RTL.node.
  Definition fn_stacksize := BTL.fn_stacksize.
  Definition fn_params := BTL.fn_params.
  Definition funsig := BTL.funsig.
End BTL_IRspec.

Module BTL_AbstractAnalysis (D : AADomain BTL_IRspec).
  Include D.

  Module DS := MultiFixpoint.Solver(Kildall.NodeSetForward)(D.VA).

Definition outcome := (VA.t * (option final))%type.
Definition outcomes := binary_tree outcome.

Definition apply_on_outcomes (f : astate -> outcomes):=
  apply_at_leaves
  (fun (oc : outcome) =>
     let (s, final) := oc in
     match filter_astate s with
     | None => binary_tree_leaf oc
     | Some s =>
         match final with
         | Some _ => binary_tree_leaf oc
         | None => f s
         end
     end).

Fixpoint transfer_iblock (pi: prog_info) (ib : iblock) (s : astate) : outcomes :=
  match ib with
  | BF fi _ => binary_tree_leaf (Astate s, Some fi)
  | Bnop _ => binary_tree_leaf (Astate s, None)
  | Bop op args res _ =>
      binary_tree_leaf (transfer_op pi op args res s, None)
  | Bload trap chunk addr args dst _ =>
      binary_tree_leaf (transfer_load pi trap chunk addr args dst s, None)
  | Bstore chunk addr args src _ =>
      binary_tree_leaf (transfer_store pi chunk addr args src s, None)
  | Bseq b1 b2 =>
      apply_on_outcomes (transfer_iblock pi b2)
                        (transfer_iblock pi b1 s)
  | Bcond cond args ifso ifnot _ =>
      let (sso, snot) := transfer_condition pi cond args s in
      match filter_astate sso, filter_astate snot with
      | None, None => binary_tree_leaf (VA.bot, None)
      | Some sso, None => transfer_iblock pi ifso sso
      | None, Some snot => transfer_iblock pi ifnot snot
      | Some sso, Some snot =>
          binary_tree_split (transfer_iblock pi ifso sso)
                            (transfer_iblock pi ifnot snot)
      end
  end.

Definition final_outcome := (exit * VA.t)%type.
Definition final_outcomes := binary_tree (list final_outcome).

Definition transfer_final (pi: prog_info) (fi : final) (s : astate): list final_outcome :=
  match fi with
  | Bgoto exit => (exit, Astate s) :: nil
  | Breturn arg => nil
  | Bcall sig reg_ident args res exit =>
      (exit, transfer_call pi args res s) :: nil
  | Btailcall sig reg_ident args => nil
  | Bbuiltin ef args res exit =>
      (exit, transfer_builtin pi ef args res s) :: nil
  | Bjumptable arg exits =>
      mapi (fun i exit => (exit, transfer_jumpi pi arg (Int.repr i) s)) exits
  end.

Definition transfer_block (pi: prog_info) (ib : iblock) (s : astate): final_outcomes :=
  apply_at_leaves
    (fun (oc : outcome) =>
       let (s, ofi) := oc in
       binary_tree_leaf
         (match ofi with
          | Some fi =>
              match filter_astate s with
              | None => nil
              | Some s => transfer_final pi fi s
              end
          | None => nil
          end))
    (transfer_iblock pi ib s).

Definition flat_transfer_block (pi: prog_info) (ibi : iblock_info) (s : astate) : list final_outcome :=
   flatten_outcomes (transfer_block pi ibi.(entry) s).

Definition transfer (pi: prog_info) (co : code) (n : positive) (va : VA.t) : list final_outcome :=
  match filter_astate va with
  | None => nil
  | Some s =>
      match PTree.get n co with
      | Some ibi => flat_transfer_block pi ibi s
      | None => nil
      end
  end.

Definition analyze (pi : prog_info) (f : function) : PMap.t D.VA.t :=
  match
    DS.solution_opt (widen_node f)
                    (transfer pi f.(fn_code))
                    ((f.(fn_entrypoint), entry_state f)::nil)
  with
  | Some solution => solution
  | None => PMap.init default_top
  end.

Section SOUNDNESS.


Lemma filter_Astate [s s']:
  filter_astate s = Some s' ->
  s = Astate s'.
Proof.
  apply filter_iff_Astate.
Qed.

Lemma Astate_filter s:
  filter_astate (Astate s) = Some s.
Proof.
  apply filter_iff_Astate. reflexivity.
Qed.

Lemma Astate_inj s s'
  (H : Astate s = Astate s'):
  s = s'.
Proof.
  apply (f_equal filter_astate) in H.
  rewrite !Astate_filter in H; injection H; auto.
Qed.

Section ANALYSIS.

Lemma transfer_strict pi co n:
  transfer pi co n VA.bot = nil.
Proof.
  unfold transfer. rewrite filter_astate_bot. reflexivity.
Qed.

Lemma analyze_successor [pi f n s ibi n' s']
  (ANALYZE : (analyze pi f)!!n = Astate s)
  (AT : f.(fn_code)!n = Some ibi)
  (IN : In (n', s') (flat_transfer_block pi ibi s)):
  VA.ge (analyze pi f)!!n' s'.
Proof.
  unfold analyze in *; intros.
  destruct DS.solution_opt eqn:SOL.
    2:{ rewrite PMap.gi. apply ge_default_top. }
  pose proof (DS.solution_stable _ _ (transfer_strict _ _) _ _ SOL) as STABLE.
  apply STABLE with (n := n).
  unfold transfer.
  rewrite ANALYZE, AT, Astate_filter.
  exact IN.
Qed.

Lemma analyze_entrypoint [pi ge gh sp e m f s0]
  (ENTRY: entry_state f = Astate s0)
  (MATCH: asmatch pi ge gh sp e m s0):
  exists s1,
    (analyze pi f)!!(fn_entrypoint f) = Astate s1 /\
    asmatch pi ge gh sp e m s1.
Proof.
  set (res := (analyze pi f)!!(fn_entrypoint f)).
  assert (A : VA.ge res (entry_state f)). {
    unfold res, analyze.
    destruct DS.solution_opt as [res'|] eqn:FIX.
    - eapply DS.solution_includes_initial. eassumption.
      constructor. reflexivity.
    - rewrite PMap.gi. apply ge_default_top.
  }
  rewrite ENTRY in A.
  destruct (filter_astate res) as [s1|] eqn:ANALYZE'.
    2:{ exfalso.
        exploit filter_ge; eauto.
        rewrite Astate_filter; discriminate 1. }
  rewrite (filter_Astate ANALYZE') in *.
  exists s1; split. reflexivity.
  eapply asmatch_ge in A; eauto.
Qed.

Lemma analyze_succ [pi ge gh sp e m f n s0 ibi n' s1]
  (ANALYZE : (analyze pi f)!!n = Astate s0)
  (AT : f.(fn_code)!n = Some ibi)
  (IN : In (n', Astate s1) (flat_transfer_block pi ibi s0))
  (MATCH : asmatch pi ge gh sp e m s1):
  exists s2,
     (analyze pi f)!!n' = Astate s2 /\
     asmatch pi ge gh sp e m s2.
Proof.
  intros.
  pose proof (analyze_successor ANALYZE AT IN) as SUCC.
  destruct (filter_astate (analyze pi f) # n') as [s2|] eqn:ANALYZE'.
  2:{ exfalso.
      exploit filter_ge; eauto.
      rewrite Astate_filter; discriminate 1. }
  apply filter_Astate in ANALYZE'; rewrite ANALYZE' in *.
  exists s2; split. reflexivity.
  eapply asmatch_ge in SUCC; eauto.
Qed.

End ANALYSIS.

Section ISTEP.
Variable pi: prog_info.
Variable ge: genv.
Variable gh: ghost_env.
Variable sp: block.

Local Notation amatch := (asmatch pi ge gh sp).

Definition match_outcomes e m (ofi : option final) : outcomes -> Prop :=
  match_tree
    (fun l : outcome =>
       let (s, ofi') := l in
       match filter_astate s with
       | Some s => amatch e m s /\ ofi = ofi'
       | None => False
       end).

Lemma match_outcomes_iff_exists e m ofi tr:
  match_outcomes e m ofi tr <->
  exists s,
    In_tree (Astate s, ofi) tr /\ amatch e m s.
Proof.
  induction tr; cbn.
  - case a as (v & ofi').
    case (filter_astate v) as [s|] eqn:FLT.
    + apply filter_Astate in FLT; subst v.
      split; [intros (? & <-)
             |intros (? & EQ & ?); injection EQ as EQ ->; apply Astate_inj in EQ as ->];
             eauto.
    + split; [intros []|intros (? & EQ & _)].
      revert FLT; injection EQ as -> _.
      rewrite Astate_filter; discriminate 1.
  - rewrite IHtr1. rewrite IHtr2.
    clear IHtr1. clear IHtr2.
    split; [intros [[s]|[s]]|intros (s & [|] & ?); [left|right]]; exists s; tauto.
Qed.

Lemma match_apply_on_outcomes1 oc1 f2 e1 m1 fi1
    (MATCH1 : match_outcomes e1 m1 (Some fi1) oc1):
    match_outcomes e1 m1 (Some fi1) (apply_on_outcomes f2 oc1).
Proof.
  induction oc1; cbn; intros.
  - destruct a as (s1, ofi1).
    revert MATCH1. unfold match_outcomes; simpl.
    case (filter_astate s1) eqn:FLT. 2:contradiction.
    intros (MATCH & <-); simpl; rewrite FLT; tauto.
  - destruct MATCH1 as [MATCH1_LEFT | MATCH1_RIGHT].
    + left. apply IHoc1_1; auto.
    + right. apply IHoc1_2; auto.
Qed.

Lemma match_apply_on_outcomes2 oc1 f2 e1 m1 e2 m2 ofi2
    (MATCH1 : match_outcomes e1 m1 None oc1)
    (MATCH2 : forall s1, amatch e1 m1 s1 ->
                match_outcomes e2 m2 ofi2 (f2 s1)):
    match_outcomes e2 m2 ofi2 (apply_on_outcomes f2 oc1).
Proof.
  induction oc1; cbn; intros.
  - destruct a as (s1, ofi1).
    revert MATCH1. unfold match_outcomes; simpl.
    case (filter_astate s1) eqn:FLT. 2:contradiction.
    intros (MATCH & <-); simpl.
    apply MATCH2; exact MATCH.
  - destruct MATCH1 as [MATCH1_LEFT | MATCH1_RIGHT].
    + left. apply IHoc1_1; auto.
    + right. apply IHoc1_2; auto.
Qed.

Lemma transfer_iblock_correct gh0 ib e m e' m' ofi
  (STEP : iblock_istep AnnotTrap ge (Vptr sp Ptrofs.zero) gh0 e m ib e' m' ofi):
  forall s (MATCH : amatch e m s),
    match_outcomes e' m' ofi (transfer_iblock pi ib s) /\
    succ_state pi ge sp gh m gh m'.
Proof.
  Local Ltac slv := rewrite Astate_filter; intuition auto using succ_state_refl.
  induction STEP; cbn; intros.
  - slv.
  - slv.
  - eapply transfer_op_correct in EVAL as (s1 & -> & ?); eauto. slv.
  - inv UDP.
    eapply transfer_load_correct in LOAD as (s1 & -> & ?); eauto. slv.
  - eapply transfer_store_correct in STORE as (s1 & -> & ?); eauto. slv.
  - case (IHSTEP _ MATCH) as (IH & SUCC). split. 2:exact SUCC.
    apply match_apply_on_outcomes1.
    apply IH; auto.
  - case (IHSTEP1 _ MATCH) as (IH1 & SUCC1).
    split.
    + eapply match_apply_on_outcomes2.
      * exact IH1.
      * apply IHSTEP2.
    + apply match_outcomes_iff_exists in IH1 as (s1 & _ & MATCH1).
      case (IHSTEP2 _ MATCH1) as (_ & SUCC2).
      eapply succ_state_trans; eauto.
  - eapply transfer_condition_correct in EVAL as TR; eauto. case TR as (s1 & TR & MATCH1).
    specialize (IHSTEP _ MATCH1) as (IH & SUCC).
    split. 2:exact SUCC.
    destruct transfer_condition as (sso, snot).
    destruct b; rewrite TR, Astate_filter; case filter_astate as [?|] in |-*; cbn; tauto.
Qed.

End ISTEP.

Section STEP.
Variable pi: prog_info.
Variable ge: genv.

Local Notation amatch := (asmatch pi ge).

Inductive sound_stack : ghost_env -> list stackframe -> mem -> block -> Prop :=
  | sound_stack_nil
        gh m bound:
        sound_stack gh nil m bound
  | sound_stack_call
        gh res f sp pc e stk m bound
        (MATCH : forall gh_ce1 m' vres
                   (VRS : valid_returnstate pi ge gh_ce1 m' vres)
                   (SUCC : succ_state pi ge bound gh m gh_ce1 m'),
                 exists aa gh1,
                   (analyze pi f) !! pc = Astate aa /\
                   amatch gh1 sp (e#res <- vres) m' aa /\
                   sound_stack gh1 stk m' sp):
        sound_stack gh (Stackframe res f (Vptr sp Ptrofs.zero) pc e :: stk) m bound.

Inductive sound_state_base (gh : ghost_env) : state -> Prop :=
  | sound_regular_state
        stk f sp pc e m _bc aa
        (STK : sound_stack gh stk m sp)
        (AN : (analyze pi f)!!pc = Astate aa)
        (MATCH : amatch gh sp e m aa):
      sound_state_base gh (State stk f (Vptr sp Ptrofs.zero) pc e m _bc)
  | sound_call_state
        stk fd args m _bc
        (STK : sound_stack gh stk m (Mem.nextblock m))
        (VCS : valid_callstate pi ge gh m args):
      sound_state_base gh (Callstate stk fd args m _bc)
  | sound_return_state
        stk v m bound _bc
        (STK : sound_stack gh stk m bound)
        (VRS : valid_returnstate pi ge gh m v):
      sound_state_base gh (Returnstate stk v m _bc).

Lemma sound_stack_succ_imp [gh gh' stk m m' bound bound']
  (STACK : sound_stack gh stk m bound)
  (IMP : forall gh1 m1, succ_state pi ge bound' gh' m' gh1 m1 -> succ_state pi ge bound gh m gh1 m1):
  sound_stack gh' stk m' bound'.
Proof.
  inversion STACK; constructor; intros; eauto.
Qed.

Lemma sound_stack_succ [gh gh' stk m m' bound]
  (SUCC : succ_state pi ge bound gh m gh' m')
  (STACK : sound_stack gh stk m bound):
  sound_stack gh' stk m' bound.
Proof.
  eapply sound_stack_succ_imp; eauto using succ_state_trans.
Qed.


Lemma sound_succ_state gh pc s0 ibi s1 stk f sp pc' e' m' _bc
  (ANALYZE : (analyze pi f)!!pc = Astate s0)
  (AT : f.(fn_code)!pc = Some ibi)
  (IN : In (pc', Astate s1) (flat_transfer_block pi ibi s0))
  (MATCH : amatch gh sp e' m' s1)
  (STACK : sound_stack gh stk m' sp):
  sound_state_base gh (State stk f (Vptr sp Ptrofs.zero) pc' e' m' _bc).
Proof.
  intros. exploit analyze_succ; eauto. intros (s2 & AN & AM).
  econstructor; eauto.
Qed.

Lemma propagate_to_final ib fin s0 s1 x
    (MID : match_tree (fun l : outcome => l = (Astate s1, Some fin))
                      (transfer_iblock pi (entry ib) s0))
    (END : In x (transfer_final pi fin s1)):
    In x (flat_transfer_block pi ib s0).
Proof.
  unfold flat_transfer_block.
  rewrite In_binary_tree_flatten_outcomes.
  unfold transfer_block.
  apply apply_match_tree with (P := (fun l : outcome => l = (Astate s1, Some fin))).
  - intros. subst; rewrite Astate_filter. exact END.
  - assumption.
Qed.


Definition final_preserv_gh (f : final) : bool :=
  match f with
  | Bgoto _ | Bjumptable _ _ => true
  | _ => false
  end.

Lemma sound_step_base_exec_iblock gh0 stack f sp pc rs m _bc0 _bc2 trace gh ib rs' m' fin st'
  (SOUND: sound_state_base gh (State stack f sp pc rs m _bc0))
  (PC: (fn_code f) ! pc = Some ib)
  (ISTEP: iblock_istep AnnotTrap ge sp gh0 rs m (entry ib) rs' m' (Some fin))
  (FINAL: final_step ge stack f sp rs' m' _bc2 fin trace st'):
  if final_preserv_gh fin
  then sound_state_base gh st'
  else exists gh', sound_state_base gh' st'.
Proof.
  inv SOUND.
  eapply transfer_iblock_correct in ISTEP as (MATCH_ISTEP & SUCC); eauto.
  rewrite match_outcomes_iff_exists in MATCH_ISTEP.
  case MATCH_ISTEP as (aa' & IN' & MATCH').
  unfold In_tree in IN'.
  eapply sound_stack_succ in STK; eauto. clear SUCC.
  clear MATCH.
  inversion FINAL; subst; simpl; try rename bc' into _bc'.
  - (* goto *)
    eapply sound_succ_state; eauto.
    eapply propagate_to_final; eauto.
    left. reflexivity.

  - (* return *)
    inv H.
    ecase sound_return as (gh' & VRS & SUCC); eauto.
    exists gh'; econstructor; eauto.
    eapply sound_stack_succ; eauto.
  
  - (* call *)
    eapply propagate_to_final in IN'.
      2:{ cbn. left. reflexivity. }
    ecase transfer_call_correct as (gh_ce0 & VCS & RT); eauto.
    exists gh_ce0; constructor; eauto.
    econstructor.
    intros gh_ce1 m_ce1 vres VRS SUCC.
    ecase RT as (s1 & gh_cr1 & TR & MATH_CR1 & SUCC1); eauto.
    rewrite TR in IN'.
    eapply (sound_stack_succ SUCC1) in STK.
    ecase analyze_succ as (s1' & -> & MATCH_CR1'); eauto.

  - (* tailcall *)
    inv H1.
    ecase sound_tailcall as (gh' & VCS & IMP); eauto.
    exists gh'; constructor; eauto.
    exact (sound_stack_succ_imp STK IMP).

  - (* builtin *)
    eapply propagate_to_final in IN'.
      2:{ simpl. left. reflexivity. }
    ecase transfer_builtin_correct as (aa2 & gh' & TR & MATCH2 & SUCC); eauto.
    rewrite TR in IN'.
    ecase analyze_succ as (s2 & ? & ?); eauto.
    exists gh'; econstructor; eauto.
    eapply sound_stack_succ; eauto.

  - (* jumptable *)
    eapply propagate_to_final in IN'.
      2:{ simpl.
          eapply list_nth_z_in.
          eapply list_nth_z_mapi.
          eassumption. }
    rewrite Int.repr_unsigned in IN'.
    ecase transfer_jumpi_correct as (aa2 & TR & MATCH2); eauto.
    rewrite TR in IN'.
    ecase analyze_succ as (s2 & ? & ?); eauto.
    econstructor; eauto.
Qed.

Theorem sound_step_base st t st' gh
  (STEP: BTL.step AnnotTrap ge st t st')
  (SOUND: sound_state_base gh st):
  exists gh', sound_state_base gh' st'.
Proof.
  inversion STEP; subst; rename bc into _bc; try rename bc' into _bc'; inversion SOUND; subst.
  - (* exec_iblock *)
    case STEP0 as (rs' & m' & fin & ISTEP & FINAL).
    exploit sound_step_base_exec_iblock; eauto.
    case final_preserv_gh; eauto.

  - (* internal call *)
    ecase function_init as (aa & gh' & ENTRY & MATCH & SUCC); eauto.
    ecase analyze_entrypoint as (aa' & ? & ?); eauto.
    exists gh'; econstructor; eauto.
    apply Mem.alloc_result in ALLOC; subst stk.
    eapply sound_stack_succ; eauto.

   - (* external call *)
    ecase sound_external_call as (gh' & VRS & SUCC); eauto.
    exists gh'; econstructor; eauto.
    eapply sound_stack_succ; eauto.

  - (* return *)
    inv STK.
    ecase MATCH as (aa & gh1 & AN & MATCH1 & SUCC); eauto using succ_state_refl.
    exists gh1; econstructor; eauto.
Qed.

End STEP.

Section PROGRAM.
Variable prog: program.
Let ge := Genv.globalenv prog.

Inductive sound_state (st : state) : Prop :=
  | sound_state_intro
      (SOUND: forall cunit, linkorder cunit prog -> exists gh, sound_state_base (get_prog_info cunit) ge gh st).

Remark sound_state_inv st cunit:
  sound_state st -> linkorder cunit prog -> exists bc, sound_state_base (get_prog_info cunit) ge bc st.
Proof.
  intros. inv H. eauto.
Qed.

Theorem sound_initial st
  (INIT : initial_state prog st):
  sound_state st.
Proof.
  constructor; intros cu LINK.
  inversion INIT; subst.
  ecase initial_valid_callstate as (gh & ?); eauto.
  exists gh; repeat constructor; auto.
Qed.

Theorem sound_step st t st'
  (STEP : BTL.step AnnotTrap ge st t st')
  (SOUND : sound_state st):
  sound_state st'.
Proof.
  inversion SOUND.
  constructor; intros cu LO.
  ecase SOUND0 as [bc]; eauto.
  eapply sound_step_base; eauto.
Qed.

End PROGRAM.

End SOUNDNESS.

End BTL_AbstractAnalysis.


Annotation of the results of the analysis

Module Type BTL_AADomain_Annotate.
  Include AADomain BTL_IRspec.


  Parameter unreachable_inst_promotable : operation + condition -> list reg -> inst_promotion_info.
  Parameter unreachable_addr_aval : option ValueDomain.aval.

  Parameter op_inst_promotable : prog_info -> astate -> operation -> list reg -> inst_promotion_info.
  Parameter cond_inst_promotable : prog_info -> astate -> condition -> list reg -> inst_promotion_info.
  Parameter get_addr_aval : prog_info -> astate -> addressing -> list reg -> option ValueDomain.aval.

  Axiom op_inst_promotable_correct : forall pi ge gh sp rs m aa op prom args
    (MATCH : asmatch pi ge gh sp rs m aa)
    (PROM : op_inst_promotable pi aa op args = IPromotableOp prom)
    (ALIVE : eval_operation ge (Vptr sp Ptrofs.zero) op rs##args m <> None),
    adp_op_promotable ge (Vptr sp Ptrofs.zero) m op prom rs##args.
  
  Axiom cond_inst_promotable_correct : forall pi ge gh sp rs m aa cond prom args
    (MATCH : asmatch pi ge gh sp rs m aa)
    (PROM : cond_inst_promotable pi aa cond args = IPromotableCond prom)
    (ALIVE : eval_condition cond rs##args m <> None),
    adp_cond_promotable m cond prom rs##args.

  Axiom get_addr_aval_correct : forall pi ge gh sp rs m aa addr args aaddr a
    (MATCH : asmatch pi ge gh sp rs m aa)
    (AADDR : get_addr_aval pi aa addr args = Some aaddr)
    (EVAL : eval_addressing ge (Vptr sp Ptrofs.zero) addr rs##args = Some a),
    ValueDomain.vmatch gh a aaddr.

End BTL_AADomain_Annotate.

Module BTL_AnnotateAnalysis (D : BTL_AADomain_Annotate).
  Include D.
  Module AA := BTL_AbstractAnalysis D.
  Import IntPromotionCommon.

Definition combine_inst_promotion_info (iprom0 iprom1 : inst_promotion_info) : inst_promotion_info :=
  if match iprom1 with
     | IPromotableOp (mk_op_prom_ceq (mk_op_prom None None _ _) _ _)
     | IPromotableCond (mk_cond_prom_ceq (mk_cond_prom None None) _)
     | IPromNone
          => false
     | _ => true
     end
  then iprom1 else iprom0.

Lemma combine_inst_promotion_info_either iprom0 iprom1:
  let iprom := combine_inst_promotion_info iprom0 iprom1 in
  iprom = iprom0 \/ iprom = iprom1.
Proof.
  unfold combine_inst_promotion_info.
  destruct (_ : bool); auto.
Qed.
Global Opaque combine_inst_promotion_info.

Definition set_inst_promotion (iprom : inst_promotion_info) (ii : inst_info) : inst_info :=
  BTL.set_inst_promotion (combine_inst_promotion_info ii.(inst_promotion) iprom) ii.

Fixpoint transf_iblock_bot (ib : iblock) :=
  match ib with
  | BF _ _ | Bnop _ => ib
  | Bop op args dst iinfo =>
      Bop op args dst (set_inst_promotion (unreachable_inst_promotable (inl op) args) iinfo)
  | Bload trap chunk addr args dst iinfo =>
      Bload trap chunk addr args dst (set_addr_aval unreachable_addr_aval iinfo)
  | Bstore chunk addr args src iinfo =>
      Bstore chunk addr args src (set_addr_aval unreachable_addr_aval iinfo)
  | Bseq b1 b2 =>
      Bseq (transf_iblock_bot b1) (transf_iblock_bot b2)
  | Bcond cond args ifso ifnot iinfo =>
      Bcond cond args (transf_iblock_bot ifso) (transf_iblock_bot ifnot)
        (set_inst_promotion (unreachable_inst_promotable (inr cond) args) iinfo)
  end.

Fixpoint transf_iblock (pi: prog_info) (ib : iblock) (s : VA.t) {struct ib}:
  iblock * VA.t :=
  match filter_astate s with
  | None => (transf_iblock_bot ib, VA.bot)
  | Some aa =>
  match ib with
  | BF fi iinfo =>
      (ib, VA.bot)
  | Bnop iinfo =>
      (ib, Astate aa)
  | Bop op args res iinfo =>
      (Bop op args res (set_inst_promotion (op_inst_promotable pi aa op args) iinfo),
       transfer_op pi op args res aa)
  | Bload trap chunk addr args dst iinfo =>
      (Bload trap chunk addr args dst (set_addr_aval (get_addr_aval pi aa addr args) iinfo),
       transfer_load pi trap chunk addr args dst aa)
  | Bstore chunk addr args src iinfo =>
      (Bstore chunk addr args src (set_addr_aval (get_addr_aval pi aa addr args) iinfo),
       transfer_store pi chunk addr args src aa)
  | Bseq b1 b2 =>
      let (b1', s1) := transf_iblock pi b1 (Astate aa) in
      let (b2', s2) := transf_iblock pi b2 s1 in
      (Bseq b1' b2', s2)
  | Bcond cond args ifso ifnot iinfo =>
      let (s_ifso, s_ifnot) := transfer_condition pi cond args aa in
      let (ifso', s1) := transf_iblock pi ifso s_ifso in
      let (ifnot', s2) := transf_iblock pi ifnot s_ifnot in
      (Bcond cond args ifso' ifnot' (set_inst_promotion (cond_inst_promotable pi aa cond args) iinfo),
       VA.lub s1 s2)
  end end.

Definition transf_function (pi : prog_info) (f : function) : BTL.function :=
  let an := AA.analyze pi f in
  let code' := PTree.map (fun i ib =>
                    let ib' := fst (transf_iblock pi (entry ib) (an !! i)) in
                    mk_ibinfo ib' (binfo ib))
                  (fn_code f) in
  BTL.mkfunction (fn_sig f) (fn_params f) (fn_stacksize f) code' (fn_entrypoint f) (fn_gm f) (fn_info f).

Definition transf_fundef (pi : prog_info) (f : fundef) : fundef :=
  AST.transf_fundef (transf_function pi) f.

Definition transf_program (p : program) : BTL.program :=
  AST.transform_program (transf_fundef (get_prog_info p)) p.


Definition match_prog (p tp: program) :=
  match_program (fun cu f tf => tf = transf_fundef (get_prog_info cu) f) eq p tp.

Definition pass := mkpass match_prog.

Lemma transf_program_match p:
  match_prog p (transf_program p).
Proof.
  apply match_transform_program_contextual; reflexivity.
Qed.

Section SOUNDNESS.

Section ISTEP.
Variable pi: prog_info.
Variable ge0 ge1 : genv.
Variable gh: ghost_env.
Variable sp: block.
Hypothesis PRESERVED: forall s : ident, Genv.find_symbol ge1 s = Genv.find_symbol ge0 s.

Local Notation amatch := (asmatch pi ge0 gh sp).

Lemma transf_iblock_correct gh0 ib aa e m e' m' ofi
  (MATCH : amatch e m aa)
  (STEP : iblock_istep AnnotTrap ge0 (Vptr sp Ptrofs.zero) gh0 e m ib e' m' ofi):
  let (ib', aa1) := transf_iblock pi ib (Astate aa) in
  (ofi = None -> exists aa1', aa1 = Astate aa1' /\ amatch e' m' aa1') /\
  iblock_istep AnnotTrap ge1 (Vptr sp Ptrofs.zero) gh e m ib' e' m' ofi.
Proof.
  revert aa MATCH; induction STEP; simpl; do 2 intro; simpl; rewrite ?AA.Astate_filter.
  - (* exec_final *)
    split. discriminate 1. constructor.
  - (* exec_nop *)
    split. eexists; split. reflexivity. assumption. constructor.
 - (* exec_op *)
   split.
   + ecase transfer_op_correct; eauto.
   + constructor.
     * erewrite eval_operation_preserved; eauto.
     * apply AssertTrue; inv ADP.
       unfold adp_op in *; simpl; rewrite if_Some_iff in *; intro.
       rewrite adp_op_promotable_preserved with (ge1 := ge0); eauto.
       ecase combine_inst_promotion_info_either as [-> | ->].
         { apply A_TRUE. }
       intro H.
       eapply op_inst_promotable_correct; eauto.
       -- destruct op_inst_promotable; inv H; reflexivity.
       -- unfold BTL.fundef in EVAL; unfold BTL_IRspec.function; rewrite EVAL; congruence.
 - (* exec_load *)
   inv UDP.
   split.
   + ecase transfer_load_correct; eauto.
   + econstructor.
     * eapply has_loaded_preserved; eauto.
     * apply MayUndefTrue.
       unfold udp_load, adp_addr_match; simpl.
       do 2 (apply if_Some_iff; intros ? ?).
       eapply get_addr_aval_correct. 1,2:eauto.
       erewrite eval_addressing_preserved; eauto.
 - (* exec_store *)
   pose proof (EVAL1 := EVAL).
   rewrite eval_addressing_preserved with (ge1 := ge1) in EVAL1 by auto.
   split.
   + ecase transfer_store_correct as (aa1 & ? & ? & _); eauto.
   + econstructor; simpl; eauto.
     apply AssertTrue.
     unfold adp_store, adp_addr_match; rewrite EVAL1; simpl.
     apply if_Some_iff; intros ? ?.
     eapply get_addr_aval_correct; eauto.
 - (* exec_seq_stop *)
   specialize (IHSTEP aa).
   destruct (transf_iblock _ b1) as [b1' s1];
       case (transf_iblock _ b2) as [b2' s2].
   (split; [discriminate 1|constructor; apply IHSTEP; auto]).
 - (* exec_seq_continue *)
   specialize (IHSTEP1 aa).
   destruct (transf_iblock _ b1) as [b1' s1].
   case IHSTEP1 as ((aa1 & ? & MATCH1) & STEP1'); auto; subst.
   specialize (IHSTEP2 aa1).
   destruct (transf_iblock _ b2) as [b2' s2].
   case IHSTEP2 as (? & ?); eauto.
   split; auto.
   econstructor; eauto.
 - (* exec_cond *)
   assert (adp_cond m cond rs##args (set_inst_promotion (cond_inst_promotable pi aa cond args) iinfo)). {
     inv ADP.
     unfold adp_cond; simpl.
     ecase combine_inst_promotion_info_either as [-> | ->].
       { apply A_TRUE. }
     case cond_inst_promotable eqn:PROM; simpl; trivial.
     eapply cond_inst_promotable_correct; eauto. congruence.
   }
   ecase transfer_condition_correct as (aa1 & ? & MATCH1); eauto.
   destruct transfer_condition as (s_so & s_not).
   destruct (transf_iblock _ ifso) as [ifso' s1] eqn:eq_so,
            (transf_iblock _ ifnot) as [ifnot' s2] eqn:eq_not.
   set (Goal1 := exists (aa2' : astate), _).
   assert (GOAL1 : forall aa2, s1 = Astate aa2 \/ s2 = Astate aa2 ->
                     amatch rs' m' aa2 -> Goal1). {
     unfold Goal1; intros aa2 aa2_eq MATCH2.
     assert (Ge: VA.ge (VA.lub s1 s2) (Astate aa2))
       by (case aa2_eq as [<-|<-]; auto using VA.ge_lub_left, VA.ge_lub_right).
      ecase (filter_astate (VA.lub s1 s2)) as [aa2'|] eqn:FLT.
        2:{ exfalso. apply filter_ge in Ge; auto. rewrite AA.Astate_filter in Ge; congruence. }
      rewrite (AA.filter_Astate FLT) in *.
      exists aa2'; split. reflexivity.
      eapply asmatch_ge; eauto.
   }
   destruct b; subst;
   epose proof (IH := IHSTEP aa1 MATCH1); rewrite ?eq_so, ?eq_not in IH; case IH as (MATCH2 & STEP');
    (split;
     [intro; case MATCH2 as (aa2 & ? & ?); auto; eapply GOAL1; eauto
     |econstructor; eauto using AssertTrue]).
Qed.

End ISTEP.

Section PROGRAM.

Variable prog tprog: program.
Hypothesis TRANSL: match_prog prog tprog.

Let ge0 := Genv.globalenv prog.
Let ge1 := Genv.globalenv tprog.

Lemma ge_find_symbol (s : ident):
  Genv.find_symbol ge1 s = Genv.find_symbol ge0 s.
Proof.
  apply (Genv.find_symbol_match TRANSL).
Qed.
Local Hint Resolve ge_find_symbol : core.

Lemma ge_find_function (fp : reg + ident) rs f:
  find_function ge0 fp rs = Some f ->
  exists cunit,
    find_function ge1 fp rs = Some (transf_fundef (get_prog_info cunit) f) /\
    linkorder cunit prog.
Proof.
  intro F0; apply (find_function_match TRANSL) in F0 as (cu & ? & ? & ? & ?); subst; eauto.
Qed.


Inductive match_stackframe: stackframe -> stackframe -> Prop :=
  | match_stackframe_intro
      sp res f pc rs cu
      (LINK: linkorder cu prog)
      : match_stackframe (BTL.Stackframe res f sp pc rs)
                         (BTL.Stackframe res (transf_function (get_prog_info cu) f) sp pc rs).

Definition match_stackframes : list stackframe -> list stackframe -> Prop :=
  list_forall2 match_stackframe.

Inductive match_states0 rm : state -> state -> Prop :=
  | match_states_norm stk stk' f pc sp rs m _bc bc
      (STACK: match_stackframes stk stk')
      : match_states0 rm (State stk f sp pc rs m _bc)
                         (State stk' (transf_function rm f) sp pc rs m bc)
  | match_states_call stk stk' f args m _bc bc
      (STACK: match_stackframes stk stk')
      : match_states0 rm (Callstate stk f args m _bc)
                         (Callstate stk' (transf_fundef rm f) args m bc)
  | match_states_return stk stk' v m _bc bc
      (STACK: match_stackframes stk stk')
      : match_states0 rm (Returnstate stk v m _bc)
                         (Returnstate stk' v m bc)
   .

Definition state_gh (s : state) : ghost_env :=
  match s with
  | State _ _ _ _ _ _ gh
  | Callstate _ _ _ _ gh
  | Returnstate _ _ _ gh
      => gh
  end.

Inductive match_states (st st' : state) : Prop :=
  match_states_intro
    (cu : program) (LINK: linkorder cu prog)
    (SOUND: AA.sound_state_base (get_prog_info cu) ge0 (state_gh st') st)
    (MATCH: match_states0 (get_prog_info cu) st st').

Lemma transfer_step st0 t st1 st0'
  (STEP : step AnnotTrap ge0 st0 t st1)
  (MATCH : match_states st0 st0')
  (OSOUND: AA.sound_state prog st0):
  exists st1',
    step AnnotTrap ge1 st0' t st1' /\ match_states st1 st1'.
Proof.
  eapply AA.sound_step in OSOUND; eauto.
  inversion STEP; try rename bc into _bc; try rename bc' into _bc'; subst; inv MATCH; inv MATCH0; simpl in *.
  2,3:exploit AA.sound_step_base; eauto; intros [bc' SOUND'].
  - (* exec_iblock *)
    case STEP0 as (rs' & m' & fin & ISTEP & FIN).
    inversion SOUND; subst.
    cut (exists st1',
          final_step ge1 stk' (transf_function (get_prog_info cu) f) (Vptr sp0 Ptrofs.zero) rs' m' bc fin t st1' /\
          match_states st1 st1'). {
      intros (st1' & FIN' & ?); exists st1'; repeat split; auto.
      eapply exec_iblock.
      - etransitivity. apply PTree.gmap.
        rewrite PC, AN; simpl; reflexivity.
      - simpl.
        exists rs', m', fin; split; auto.
        eapply transf_iblock_correct with (ge0 := ge0) (ge1 := ge1) in ISTEP as ISTEP'; eauto.
        destruct transf_iblock. apply ISTEP'.
    }
    exploit AA.sound_step_base_exec_iblock; eauto; intros SOUND'.
    inversion FIN; try rename bc' into _bc'; subst; simpl in *.
    2,5:case SOUND' as [bc' SOUND'].
    1,2,6:solve [eexists; split;
                  [econstructor;eauto
                  |exists cu; [auto | simpl;apply SOUND' | constructor;auto]]].
    + (* exec_Bbuiltin *)
      eexists; split; [|exists cu; auto].
      * econstructor; eauto.
        -- eapply Events.eval_builtin_args_preserved; eauto.
        -- eapply Events.external_call_symbols_preserved; eauto.
           eapply (Genv.senv_match TRANSL).
      * exact SOUND'.
      * constructor; auto.
    (* When executing calls, [ge_find_function] gives a new [cu'] for the callee.
       We recover the soundness with respect to the analysis with [cu'] using [OSOUND]. *)

    + (* exec_Bcall *)
      apply ge_find_function in H as (cu' & ? & ?).
      clear SOUND'; apply AA.sound_state_inv with (cunit := cu') in OSOUND as [bc' SOUND']; auto.
      eexists; split; [|exists cu'; auto].
      * econstructor. eassumption.
        case fd; reflexivity.
      * exact SOUND'.
      * repeat (constructor; auto).
    + (* exec_Btailcall *)
      apply ge_find_function in H as (cu' & ? & ?).
      clear SOUND'; apply AA.sound_state_inv with (cunit := cu') in OSOUND as [bc' SOUND']; auto.
      eexists; split; [|exists cu'; auto].
      * econstructor; eauto.
        case fd; reflexivity.
      * exact SOUND'.
      * constructor; auto.

  - (* exec_function_internal *)
    eexists; split; [|exists cu; auto].
    + eapply exec_function_internal with (f:=transf_function (get_prog_info cu) f); simpl; eauto.
    + exact SOUND'.
    + constructor; auto.

  - (* exec_function_external *)
    eexists; split; [|exists cu; auto].
    + eapply exec_function_external; auto.
      eapply Events.external_call_symbols_preserved; eauto.
      eapply (Genv.senv_match TRANSL).
    + exact SOUND'.
    + constructor; auto.

  - (* exec_return *)
    clear cu LINK SOUND.
    inversion STACK as [|? ? ? ? STACK0 STACK1].
    inv STACK0.
    apply AA.sound_state_inv with (cunit := cu) in OSOUND as [bc' SOUND']; auto.
    eexists; split; [|exists cu; auto].
    + eapply exec_return.
    + exact SOUND'.
    + constructor; auto.
Qed.


Theorem transf_program_correct:
  forward_simulation (sem AnnotTrap prog) (sem AnnotTrap tprog).
Proof.
  eapply forward_simulation_step
    with (fun s s' => AA.sound_state prog s /\ match_states s s');
    simpl.
  - apply (Genv.senv_match TRANSL).
  - intros ? INI.
    apply AA.sound_initial in INI as SOUND.
    inv INI.
    ecase (Genv.find_funct_ptr_match TRANSL) as (cu & ? & ? & ? & ?); eauto; subst.
    ecase AA.sound_state_inv as [bc SOUND_b]; eauto.
    eexists; repeat split.
    + econstructor.
      * eapply (Genv.init_mem_match TRANSL); eauto.
      * rewrite ge_find_symbol, (match_program_gen_main _ _ _ _ _ TRANSL). eassumption.
      * eassumption.
      * destruct f; simpl in *; assumption.
    + intros; exploit AA.sound_state_inv; eauto.
    + exists cu; eauto. do 2 constructor.
  - intros ? ? ? [_ MATCH].
    inversion 1; subst; inv MATCH; inv MATCH0; inv STACK.
    constructor.
  - intuition.
    ecase transfer_step as (s2' & ? & ?); eauto.
    exists s2'; intuition.
    eapply AA.sound_step; eauto.
Qed.

End PROGRAM.

End SOUNDNESS.

Program Definition ipass : IPass (BTL.sem AnnotTrap) (BTL.sem AnnotTrap) :=
  {|
    ipass_spec := pass;
    ipass_impl := fun p => Errors.OK (transf_program p);
  |}.
Next Obligation.
apply transf_program_match; assumption. Qed.
Next Obligation.
apply transf_program_correct; assumption. Qed.

End BTL_AnnotateAnalysis.