Module RTL_AnalysisLib


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

Module RTL_IRspec <: IRspec.
  Definition function := RTL.function.
  Definition node := RTL.node.
  Definition fn_stacksize := RTL.fn_stacksize.
  Definition fn_params := RTL.fn_params.
  Definition fn_sig := RTL.fn_sig.
  Definition funsig := RTL.funsig.
End RTL_IRspec.

Module RTL_AbstractAnalysis (D : AADomain RTL_IRspec).
  Include D.

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

Definition transfer_instr (pi : prog_info) (i : instruction) (aa : astate) : list (node * VA.t) :=
  match i with
  | Inop s => (s, Astate aa) :: nil
  | Iop op args res s => (s, transfer_op pi op args res aa) :: nil
  | Iload trap chunk addr args dst s => (s, transfer_load pi trap chunk addr args dst aa) :: nil
  | Istore chunk addr args src s => (s, transfer_store pi chunk addr args src aa) :: nil
  | Icall sig ros args res s => (s, transfer_call pi args res aa) :: nil
  | Itailcall sig ros args => nil
  | Ibuiltin ef args res s => (s, transfer_builtin pi ef args res aa) :: nil
  | Icond cond args s1 s2 _ =>
      let (aa1, aa2) := transfer_condition pi cond args aa in
      (s1, aa1) :: (s2, aa2) :: nil
  | Ijumptable arg tbl =>
      mapi (fun i exit => (exit, transfer_jumpi pi arg (Int.repr i) aa)) tbl
  | Ireturn arg =>
      nil
  | Iassert cond args s =>
      (s, Astate aa)::nil
  end.

Definition forget rl (xx : VA.t) : VA.t :=
  match filter_astate xx with
  | Some yy => Astate (eforget rl yy)
  | None => xx
  end.

Lemma forget_ge: forall rl xx, VA.ge (forget rl xx) xx.
Proof.
  intros. unfold forget.
  destruct filter_astate eqn:ASTATE.
  - rewrite D.filter_iff_Astate in ASTATE. subst xx.
    apply D.eforget_ge.
  - apply VA.ge_refl. apply VA.eq_refl.
Qed.
  
Definition transfer0 (pi : prog_info) (f: function) (pc: node) (aa : VA.t) : list (node * VA.t) :=
  match filter_astate aa with
  | None => nil
  | Some aa =>
  match f.(fn_code)!pc with
  | None => nil
  | Some i => transfer_instr pi i aa
  end end.

Definition transfer lu pi f pc aa :=
  let zz := transfer0 pi f pc aa in
  match lu ! pc with
  | Some rl => List.map (fun pp => (fst pp, forget rl (snd pp))) zz
  | None => zz
  end.
  
Definition analyze (pi: prog_info) (f: function): PMap.t D.VA.t :=
  let lu := Liveness.last_uses f in
  match DS.solution_opt (widen_node f) (transfer lu pi f) ((f.(fn_entrypoint), entry_state f) :: nil) with
  | Some solution => solution
  | None => PMap.init default_top
  end.

Module AALemmas := AADomainLemmas(RTL_IRspec)(D).

Section SOUNDNESS.
Import AALemmas.

Section ANALYSIS.

Lemma transfer_strict lu pi f n:
  transfer lu pi f n VA.bot = nil.
Proof.
  unfold transfer, transfer0.
  rewrite filter_astate_bot. cbn.
  destruct (lu ! n); reflexivity.
Qed.

Lemma analyze_successor [pi f n i aa n' aa']
  (ANALYZE : (analyze pi f)!!n = Astate aa)
  (AT : f.(fn_code)!n = Some i)
  (IN : In (n', aa') (transfer_instr pi i aa)):
  VA.ge (analyze pi f)!!n' aa'.
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.
  destruct ((Liveness.last_uses f) ! n) as [rl | ] eqn:LAST_USES.
  - apply VA.ge_trans with (y:=forget rl aa').
    + apply STABLE with (n := n).
      rewrite ANALYZE.
      unfold transfer, transfer0; rewrite Astate_filter, AT.
      rewrite LAST_USES.
              
      change (n', (forget rl aa')) with ((fun pp => (fst pp, forget rl (snd pp))) (n', aa')).
      apply in_map. { apply IN. }
    + apply forget_ge.
  - apply STABLE with (n := n).
    rewrite ANALYZE.
    unfold transfer, transfer0; rewrite Astate_filter, AT.
    rewrite LAST_USES.
    apply 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 i n' s1]
  (ANALYZE : (analyze pi f)!!n = Astate s0)
  (AT : f.(fn_code)!n = Some i)
  (IN : In (n', Astate s1) (transfer_instr pi i 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 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 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)
  | sound_call_state
        stk fd args m
        (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)
  | sound_return_state
        stk v m bound
        (STK : sound_stack gh stk m bound)
        (VRS : valid_returnstate pi ge gh m v):
      sound_state_base gh (Returnstate stk v m).

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 i s1 stk f sp pc' e' m'
  (ANALYZE : (analyze pi f)!!pc = Astate s0)
  (AT : f.(fn_code)!pc = Some i)
  (IN : In (pc', Astate s1) (transfer_instr pi i 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').
Proof.
  intros. exploit analyze_succ; eauto. intros (s2 & AN & AM).
  econstructor; eauto.
Qed.

Theorem sound_step_base st t st' gh
  (STEP: RTL.step ge st t st')
  (SOUND: sound_state_base gh st):
  exists gh', sound_state_base gh' st'.
Proof.
  inv STEP; subst; inversion SOUND; subst.
  - (* exec_Inop *)
    exists gh.
    eapply sound_succ_state; eauto.
    left. reflexivity.
  - (* exec_Iop *)
    exists gh.
    ecase transfer_op_correct as (? & TR & ?); eauto.
    eapply sound_succ_state; eauto.
    left. rewrite TR. reflexivity.
  - (* exec_Iload *)
    exists gh.
    ecase transfer_load_correct as (? & TR & ?); eauto.
    eapply sound_succ_state; eauto.
    left. rewrite TR. reflexivity.
  - (* exec_Istore *)
    exists gh.
    ecase transfer_store_correct as (? & TR & ? & ?); eauto.
    eapply sound_succ_state; eauto using sound_stack_succ.
    left. rewrite TR. reflexivity.
  - (* exec_Icall *)
    ecase transfer_call_correct as (gh_ce0 & VCS & K); eauto.
    exists gh_ce0; constructor; eauto.
    constructor.
    intros gh_ce1 m' vres VRS SUCC.
    ecase K as (aa1 & gh_cr1 & TR & MATCH1 & SUCC1); eauto.
    ecase analyze_succ as (aa1' & ? & ?); eauto.
      { left. rewrite TR. reflexivity. }
    eapply (sound_stack_succ SUCC1) in STK.
    eauto.
  - (* exec_Itailcall *)
    ecase sound_tailcall as (gh' & VCS & SUCC); eauto.
    exists gh'; constructor; eauto.
    eapply sound_stack_succ_imp; eauto.
  - (* exec_Ibuiltin *)
    ecase transfer_builtin_correct as (? & gh' & TR & ? & ?); eauto.
    exists gh'.
    eapply sound_succ_state; eauto using sound_stack_succ.
      { left. rewrite TR. reflexivity. }
  - (* exec_Icond *)
    exists gh.
    ecase transfer_condition_correct as (? & TR & ?); eauto.
    eapply sound_succ_state; eauto.
    simpl; rewrite <- TR.
    destruct transfer_condition, b; simpl; tauto.
  - (* exec_Ijumptable *)
    exists gh.
    ecase transfer_jumpi_correct as (? & TR & ?); eauto.
    eapply sound_succ_state; eauto.
    simpl; rewrite <- TR.
    eapply list_nth_z_in.
    eapply list_nth_z_mapi in H1 as ->.
    rewrite Int.repr_unsigned; reflexivity.
  - (* exec_Ireturn *)
    ecase sound_return as (gh' & ? & ?); eauto.
    exists gh'; econstructor; eauto using sound_stack_succ.
  - (* exec_Iassert *)
    exists gh.
    eapply sound_succ_state; eauto.
    simpl; auto.
  - (* exec_function_internal *)
    ecase function_init as (? & gh' & ? & ? & ?); eauto.
    ecase analyze_entrypoint as (aa' & ? & ?); eauto.
    exists gh'; econstructor; eauto.
    apply Mem.alloc_result in H0; subst stk.
    eapply sound_stack_succ; eauto.
  - (* exec_function_external *)
    ecase sound_external_call as (gh' & VRS & SUCC); eauto.
    exists gh'; econstructor; eauto.
    eapply sound_stack_succ; eauto.
  - (* exec_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 : RTL.step 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 RTL_AbstractAnalysis.