Module BTL_SEimpl_check


Require Import Coqlib AST RTL BTL Errors.
Require Import Maps Registers OptionMonad.
Require Import BTL_Invariants BTL_SEtheory.
Require Import BTL_SEsimuref BTL_SEsimplify BTL_SEimpl_prelude.
Require Import BTL_SEimpl_SE BTL_SEimpl_SI.
Import Notations SvalNotations.
Import HConsing.
Local Open Scope impure.

Implementing the simulation test with concrete hash-consed symbolic execution


Definition phys_check {A} (x y:A) (msg: pstring): ?? unit :=
  DO b <~ phys_eq x y;;
  assert_b b msg;;
  RET tt.

Lemma phys_check_correct {A} (a b: A) msg:
  WHEN phys_check a b msg ~> _ THEN
  a = b.
Proof.
wlp_simplify. Qed.

Definition struct_check {A} (x y: A) (msg: pstring): ?? unit :=
  DO b <~ struct_eq x y;;
  assert_b b msg;;
  RET tt.

Lemma struct_check_correct {A} (a b: A) msg:
  WHEN struct_check a b msg ~> _ THEN
  a = b.
Proof.
wlp_simplify. Qed.
Global Opaque struct_check.
Local Hint Resolve struct_check_correct: wlp.

Definition option_eq_check {A} (o1 o2: option A): ?? unit :=
  match o1, o2 with
  | Some x1, Some x2 => phys_check x1 x2 "option_eq_check: data physically differ"
  | None, None => RET tt
  | _, _ => FAILWITH "option_eq_check: structure differs"
  end.

Lemma option_eq_check_correct A (o1 o2: option A): WHEN option_eq_check o1 o2 ~> _ THEN o1=o2.
Proof.
  wlp_simplify.
Qed.
Global Opaque option_eq_check.
Local Hint Resolve option_eq_check_correct:wlp.

Definition option_incl_check {A} (o1 o2: option A): ?? unit :=
  match o1, o2 with
  | Some x1, Some x2 => phys_check x1 x2 "option_incl_check: data physically differ"
  | None, _ => RET tt
  | _, _ => FAILWITH "option_incl_check: structure differs"
  end.

Lemma option_incl_check_correct A (o1 o2: option A): WHEN option_incl_check o1 o2 ~> _ THEN
  o1=o2 \/ o1 = None.
Proof.
  wlp_simplify.
Qed.
Global Opaque option_incl_check.
Local Hint Resolve option_incl_check_correct:wlp.


Import PTree.

Definition PTree_incl_check {A} (m1 m2: PTree.t A): ?? unit :=
  PTree.tree_rec2
    RET tt
    (fun t => RET tt)
    (fun t => FAILWITH "PTree_incl_check")
    (fun l1 o1 r1 l2 o2 r2 olrec orrec =>
      DO lrec <~ olrec;;
      DO rrec <~ orrec;;
      DO oc <~ option_incl_check o1 o2;;
      RET tt)
    m1 m2.

Lemma PTree_incl_check_correct A: forall (m1 m2: PTree.t A),
 WHEN PTree_incl_check m1 m2 ~> _ THEN forall x,
 m1!x = m2!x \/ m1!x = None.
Proof.
  unfold PTree_incl_check.
  induction m1 using PTree.tree_ind; induction m2 using PTree.tree_ind; intros;
  try solve [wlp_simplify].
  - rewrite unroll_tree_rec2_NE by auto; wlp_simplify.
  - rewrite unroll_tree_rec2_NN by auto; wlp_simplify.
    all:
      rewrite !gNode; destruct x;
      [ eapply IHm0; eauto | eapply IHm1; eauto | intuition ].
Qed.
Local Hint Resolve PTree_incl_check_correct: wlp.

Definition hrs_simu_check (hrs1 hrs2: ristate) : ?? unit :=
  struct_check (ris_input_init hrs1) None
    "hrs_simu_check : hrs1.(ris_input_init) isn't None";;
  PTree_incl_check (ris_sreg hrs1) (ris_sreg hrs2);;
  DEBUG("hrs_simu_check=>OK").

Lemma hrs_simu_check_spec ctx hrs1 hrs2 sis1 sis2
  (RR1 : ris_refines_ok ctx hrs1 sis1)
  (RR2 : ris_refines_ok ctx hrs2 sis2)
  (MEM : smem_equiv ctx (sis_smem sis1) (sis_smem sis2)):
  WHEN hrs_simu_check hrs1 hrs2 ~> _ THEN
  sistate_simu ctx sis1 sis2.
Proof.
  wlp_simplify.
  intros rs m (_ & MEM2 & REG2).
  split; [|split].
  - apply RR1.
  - rewrite MEM. apply MEM2.
  - intros r sv R1.
    apply REG_EQ with (r := r) in RR1, RR2.
    unfold ris_sreg_get in RR1, RR2.
    rewrite R1, H in RR1; inv RR1.
    generalize H2, (H0 r); autodestruct; try_simplify_someHyps; intros.
    case H3 as [R2|C]. 2:discriminate C.
    rewrite <- R2 in RR2; inv RR2.
    erewrite <- REG2 by eauto.
    congruence.
Qed.
Global Opaque hrs_simu_check.


Fixpoint check_list [A] (check : A -> ?? unit) (l : list A) : ?? unit :=
  match l with
  | nil => RET ()
  | x :: xs => check x;; check_list check xs
  end.

Lemma check_list_spec A check l:
  WHEN @check_list A check l ~> _ THEN
  Forall (fun x => check x ~~> ()) l.
Proof.
  induction l; wlp_simplify; destruct exta; constructor; auto.
Qed.
Global Opaque check_list.


Definition hrs_check_match_si HCF HC RRULES (m : se_mode) (gm : node -> csasv)
    (hrs0 : ristate) (rfv : rfval) (hrs1 : ristate) : ?? unit :=
  DO csis <~ si_rfv gm rfv;;
  let m' := se_mode_set_ok_check m in
  check_list (fun csi =>
    DO hrsI <~ @tr_hrs_single HCF HC RRULES m' hrs0 csi;;
    let hrsI := ris_input_init_set hrsI None in
    hrs_simu_check hrsI hrs1
  ) csis.

Lemma hrs_check_match_si_spec HCF HC RRULES m ctx gm hrs0 sis0 rfv sfv hrs1 sis1
  (RR0 : ris_refines_ok ctx hrs0 sis0)
  (RR1 : ris_refines_ok ctx hrs1 sis1)
  (RFV : rfv_refines ctx rfv sfv)
  (MEM : smem_equiv ctx (sis_smem sis0) (sis_smem sis1)):
  WHEN hrs_check_match_si HCF HC RRULES m gm hrs0 rfv hrs1 ~> _ THEN
  match_tr_si_sfv ctx gm sis0 sfv sis1.
Proof.
  unfold hrs_check_match_si, match_tr_si_sfv; wlp_seq; intros csis CSIS () CHK.
  apply si_rfv_spec in CSIS.
  erewrite <- si_sfv_set_morph by apply RFV.
  destruct si_sfv_set as (siP, siS).
  split. apply CSIS.
  intros si SI; apply CSIS in SI.
  eapply check_list_spec, Forall_forall in CHK; eauto.
  revert_wlp CHK; wlp_seq.
  intros hrsI TR () CHK _.
  eapply tr_hrs_single_correct in TR; eauto.
  eapply hrs_simu_check_spec; eauto.
Qed.
Global Opaque hrs_check_match_si.


Definition svos_simu_check (svos1 svos2: sval + qualident) :=
  match svos1, svos2 with
  | inl sv1, inl sv2 => phys_check sv1 sv2 "svos_simu_check: sval mismatch"
  | inr id1, inr id2 => phys_check id1 id2 "svos_simu_check: symbol mismatch"
  | _, _ => FAILWITH "svos_simu_check: type mismatch"
  end.

Lemma svos_simu_check_correct svos1 svos2:
  WHEN svos_simu_check svos1 svos2 ~> _ THEN
  svos1 = svos2.
Proof.
  destruct svos1; destruct svos2; wlp_simplify.
Qed.
Global Opaque svos_simu_check.
Local Hint Resolve svos_simu_check_correct: wlp.

Fixpoint builtin_arg_simu_check (bs bs': builtin_arg sval) :=
  match bs with
  | BA sv =>
    match bs' with
    | BA sv' => phys_check sv sv' "builtin_arg_simu_check: sval mismatch"
    | _ => FAILWITH "builtin_arg_simu_check: BA mismatch"
    end
  | BA_splitlong lo hi =>
    match bs' with
    | BA_splitlong lo' hi' =>
        builtin_arg_simu_check lo lo';;
        builtin_arg_simu_check hi hi'
    | _ => FAILWITH "builtin_arg_simu_check: BA_splitlong mismatch"
    end
  | BA_addptr b1 b2 =>
    match bs' with
    | BA_addptr b1' b2' =>
        builtin_arg_simu_check b1 b1';;
        builtin_arg_simu_check b2 b2'
    | _ => FAILWITH "builtin_arg_simu_check: BA_addptr mismatch"
    end
  | bs => struct_check bs bs' "builtin_arg_simu_check: basic mismatch"
  end.

Lemma builtin_arg_simu_check_correct: forall bs1 bs2,
  WHEN builtin_arg_simu_check bs1 bs2 ~> _ THEN
  bs1 = bs2.
Proof.
  induction bs1.
  all: try (wlp_simplify; subst; reflexivity).
  all: destruct bs2; wlp_simplify; congruence.
Qed.
Global Opaque builtin_arg_simu_check.
Local Hint Resolve builtin_arg_simu_check_correct: wlp.

Fixpoint list_builtin_arg_simu_check lbs1 lbs2 :=
  match lbs1, lbs2 with
  | nil, nil => RET tt
  | bs1::lbs1, bs2::lbs2 =>
    builtin_arg_simu_check bs1 bs2;;
    list_builtin_arg_simu_check lbs1 lbs2
  | _, _ => FAILWITH "list_builtin_arg_simu_check: length mismatch"
  end.

Lemma list_builtin_arg_simu_check_correct: forall lbs1 lbs2,
  WHEN list_builtin_arg_simu_check lbs1 lbs2 ~> _ THEN
  lbs1 = lbs2.
Proof.
  induction lbs1; destruct lbs2; wlp_simplify. congruence.
Qed.
Global Opaque list_builtin_arg_simu_check.
Local Hint Resolve list_builtin_arg_simu_check_correct: wlp.

Definition sfval_simu_check (sfv1 sfv2: rfval): ?? unit :=
  match sfv1, sfv2 with
  | Sgoto e1, Sgoto e2 =>
      phys_check e1 e2 "sfval_simu_check: Sgoto successors do not match"
  | Scall sig1 svos1 lsv1 res1 e1, Scall sig2 svos2 lsv2 res2 e2 =>
      phys_check e1 e2 "sfval_simu_check: Scall successors do not match";;
      phys_check sig1 sig2 "sfval_simu_check: Scall different signatures";;
      phys_check res1 res2 "sfval_simu_check: Scall res do not match";;
      svos_simu_check svos1 svos2;;
      phys_check lsv1 lsv2 "sfval_simu_check: Scall args do not match"
  | Stailcall sig1 svos1 lsv1, Stailcall sig2 svos2 lsv2 =>
      phys_check sig1 sig2 "sfval_simu_check: Stailcall different signatures";;
      svos_simu_check svos1 svos2;;
      phys_check lsv1 lsv2 "sfval_simu_check: Stailcall args do not match"
  | Sbuiltin ef1 lbs1 br1 e1, Sbuiltin ef2 lbs2 br2 e2 =>
      phys_check e1 e2 "sfval_simu_check: Sbuiltin successors do not match";;
      phys_check ef1 ef2 "sfval_simu_check: Sbuiltin ef do not match";;
      phys_check br1 br2 "sfval_simu_check: Sbuiltin br do not match";;
      list_builtin_arg_simu_check lbs1 lbs2
  | Sjumptable sv1 le1, Sjumptable sv2 le2 =>
      phys_check le1 le2 "sfval_simu_check: Sjumptable successors do not match";;
      phys_check sv1 sv2 "sfval_simu_check: Sjumptable sval do not match"
  | Sreturn osv1, Sreturn osv2 =>
      option_eq_check osv1 osv2
  | _, _ => FAILWITH "sfval_simu_check: structure mismatch"
  end.

Lemma sfval_simu_check_correct sfv1 sfv2:
  WHEN sfval_simu_check sfv1 sfv2 ~> _ THEN
  rfv_simu sfv1 sfv2.
Proof.
  destruct sfv1; destruct sfv2; simpl; wlp_simplify; congruence.
Qed.
Local Hint Resolve sfval_simu_check_correct: wlp.
Global Opaque sfval_simu_check.


Fixpoint rst_simu_check (check : ristate -> rfval -> ristate -> rfval -> meminv_annot_t -> ?? unit)
  (rst1 rst2: rstate) {struct rst1} : ?? unit :=
  match rst1, rst2 with
  | Sfinal hrs1 sfv1 _, Sfinal hrs2 sfv2 meminv_annot =>
      check hrs1 sfv1 hrs2 sfv2 meminv_annot
  | Scond cond1 lsv1 rsL1 rsR1, Scond cond2 lsv2 rsL2 rsR2 =>
      struct_check cond1 cond2 "rst_simu_check: conditions do not match";;
      phys_check lsv1 lsv2 "rst_simu_check: args do not match";;
      rst_simu_check check rsL1 rsL2;;
      rst_simu_check check rsR1 rsR2
  | _, _ => FAILWITH "rst_simu_check: simu_check failed"
  end.

Lemma rst_simu_check_spec ctx check rst1 rst2 ris1 rfv1
  (GET : get_routcome ctx rst1 = sout ris1 rfv1)
  (ROK : ris_ok ctx ris1):
  WHEN rst_simu_check check rst1 rst2 ~> _ THEN
  let (ris2, rfv2) := get_routcome ctx rst2 in
  exists an,
  check ris1 rfv1 ris2 rfv2 an ~~> ().
Proof.
  revert rst2 GET; induction rst1; intros [ ] ?; simpl; wlp_simplify.
  - injection GET as -> ->.
    destruct exta; eauto.
  - revert GET; simpl.
    destruct eval_scondition as [b|].
    + case b; simpl; intro; [eapply IHrst1_1|eapply IHrst1_2]; eauto.
    + injection 1 as <- <-; exfalso.
      eapply ris_error_not_ok, ROK.
Qed.

Global Opaque rst_simu_check.

Definition HashConsing_Fcts (_ : unit) :=
  DO hC_sval <~ hCons hSVAL;;
  DO hC_list_sval <~ hCons hLSVAL;;
  DO hC_smem <~ hCons hSMEM;;
  DO hC_okclause <~ hCons hOKCL;;
  RET (Build_HashConsingFcts hC_sval.(hC) hC_list_sval.(hC) hC_smem.(hC) hC_okclause.(hC)).

Lemma HashConsing_Instance_correct u:
  WHEN HashConsing_Fcts u~> HCF THEN
  HashConsingHyps HCF.
Proof.
  unfold HashConsing_Fcts; wlp_simplify.
  constructor; simpl; wlp_simplify.
Qed.
Global Opaque HashConsing_Fcts.

Program Definition HashConsing (u : unit): ?? { HCF : HashConsingFcts | HashConsingHyps HCF } :=
  DO HCF <~ mk_annot (HashConsing_Fcts u);;
  RET (exist _ (`HCF) _).
Next Obligation.
  eapply HashConsing_Instance_correct; eauto.
Qed.
Global Opaque HashConsing.

Definition simu_check_single (RRULES: rrules_set) (f1 f2: function)
  (ibf1 ibf2: iblock_info) (gm: gluemap) pc: ?? unit :=
creating the hash-consing tables
  DO HCF_HC <~ HashConsing tt;;
  let (HCF, HC) := HCF_HC in
  let se_mode := {| se_ok_check := false;
                    se_ok_equiv := true |} in
  let se_mode_trg := se_mode_set_ok_equiv false se_mode in
  let hrs_ini_states := @hrs_ini_states HCF HC RRULES in
  let hrexec := @hrexec HCF HC RRULES in
  let hrs_check_match_si := hrs_check_match_si HCF HC RRULES se_mode in
  let hrs_imem_subst_isol := @hrs_imem_subst_isol HCF HC RRULES in
performing the hash-consed executions
  DO ini12 <~ hrs_ini_states se_mode (gm pc);;
  let (ini1, ini2) := ini12 in
  DO ends1 <~ hrexec se_mode ibf1.(entry) ini1;;
  DO ends2 <~ hrexec se_mode_trg ibf2.(entry) ini2;;
simulation test
  rst_simu_check (fun end1 fin1 end2 fin2 mannot =>
    sfval_simu_check fin1 fin2;;
    assert_okset_incl (ris_pre end2) (ris_pre end1);;
    let endE := rset_smem (ris_smem end2) end1 in
    hrs_check_match_si (fun pc => history (gm pc)) endE fin1 endE;;
    hrs_check_match_si (fun pc => glue (gm pc)) endE fin1 end2;;
    DO ims <~ meminv_rfv (fun pc => meminv (gm pc)) fin1;;
    check_list (fun im =>
      DO mI <~ hrs_imem_subst_isol se_mode_trg end2 mannot im;;
      let (mI, mIok) := mI in
      assert_okset_incl mIok (ris_pre end1);;
      phys_check (ris_smem end1) mI "simu_check_single: memory invariant"
    ) ims
  ) ends1 ends2.

Lemma simu_check_single_correct (RRULES: rrules_set) (f1 f2: function) (ibf1 ibf2: iblock_info)
  (gm: gluemap) pc:
  WHEN simu_check_single RRULES f1 f2 ibf1 ibf2 gm pc ~> _ THEN
  match_sexec_si_all_ctx gm ibf1.(entry) ibf2.(entry) pc.
Proof.
  unfold simu_check_single; wlp_seq.
  intros [HCF HC] _; wlp_seq.
  intros (ini1, ini2) INI; wlp_seq.
  intros ends1 EXEC1 ends2 EXEC2 () CHK.
  set (se_mode := {| se_ok_check := _ |}) in *.
  intro ctx; unfold match_sexec_si.

  (* Initial states *)
  apply (hrs_ini_states_spec _ ctx) in INI as (RR_ini1 & RR_ini2).
  
  (* Symbolic execution of the source program *)
  eapply hrexec_spec in EXEC1; eauto.
  set (ss1 := sexec (entry ibf1) (sis_source (gm pc))) in *.
  inversion EXEC1 as [end1 fin1 sis1 sfv1 RR_end1 RFV1 RGET1 SGET1].
  split. {
    (* hypotheses of the source *)
    apply OK_EQUIV in RR_end1; inversion RR_end1.
    exact SHY.
  }
  intro SOK; apply (ok_ref_si SOK) in RR_end1; auto; clear SOK.
  forward1 RFV1. { apply RR_end1. }
  clear SGET1; symmetry in RGET1.

  (* Symbolic execution of the target program *)
  eapply hrexec_spec in EXEC2; eauto.
    2:{ eapply ris_refines_change_mode; eauto. }
  set (ss2 := sexec (entry ibf2) (sis_target (gm pc))) in *.
  inversion EXEC2 as [end2 fin2 sis2 sfv2 RR_end2 RFV2 RGET2 SGET2].
  clear SGET2; symmetry in RGET2.

  (* Pointwise comparison of the final states *)
  eapply rst_simu_check_spec in CHK; eauto using ROK.
  rewrite RGET2 in CHK; case CHK as (mannot & CHK).
  revert_wlp CHK; wlp_seq.
  intros () CHK_sfv () CHK_ok.
  set (endE := rset_smem (ris_smem end2) end1).
  intros () CHK_H () CHK_G ims IMS () CHK_M _.
  
  (* The target state is ok *)
  apply ok_ref in RR_end2.
    2:{ apply assert_okset_incl_correct in CHK_ok; apply CHK_ok, RR_end1. }
  forward1 RFV2. { apply RR_end2. }
  do 2 (split; [apply RR_end2|]).

  (* State with the memory of the target and the registers of the source *)
  set (sisE := set_smem (sis_smem sis2) sis1).
  assert (RR_E : ris_refines_ok ctx endE sisE). {
    constructor; try apply RR_end1.
    - apply ok_set_smem; split. apply RR_end1. apply RR_end2.
    - apply RR_end2.
  }

  (* Check the final value *)
  split. {
    eapply sfval_simu_check_correct in CHK_sfv.
    eapply rfv_simu_correct; eauto.
  }

  (* Check the history and glue *)
  do 2 (split; [solve [eapply hrs_check_match_si_spec; eauto]|]).

  (* Check the memory invariant *)
  apply meminv_rfv_spec in IMS.
  erewrite <- meminv_sfv_set_morph by apply RFV1.
  destruct meminv_sfv_set as (miP, miS).
  split. apply IMS.
  intros im IM; apply IMS in IM.
  eapply check_list_spec, Forall_forall in CHK_M; eauto.
  revert_wlp CHK_M; wlp_seq.
  intros (mI, mIok) MI; wlp_seq.
  intros () CHK_okM () CHK_M _.
  eapply hrs_imem_subst_isol_spec in MI as (_ & <-); eauto.
    { apply assert_okset_incl_correct in CHK_okM; apply CHK_okM, RR_end1. }
  apply phys_check_correct in CHK_M as <-.
  symmetry; apply RR_end1.
Qed.
Global Opaque simu_check_single.
Local Hint Resolve simu_check_single_correct: wlp.

Coerce simu_check_single into a pure function.



Definition aux_simu_check (RRULES: rrules_set) (f1 f2: function) (ibf1 ibf2: iblock_info)
  (gm: gluemap) (pc: node): ?? unit :=
   XDEBUG pc (fun pc => DO name_pc <~ string_of_Z (Zpos pc);; RET ("simu_check_single?:BTL block n°" +; name_pc)%string);;
   simu_check_single RRULES f1 f2 ibf1 ibf2 gm pc;;
   DEBUG("simu_check_single=>OK").

Lemma aux_simu_check_correct (RRULES: rrules_set) (f1 f2: function) (ibf1 ibf2: iblock_info)
  (gm: gluemap) (pc: node):
  WHEN aux_simu_check RRULES f1 f2 ibf1 ibf2 gm pc ~> _ THEN
    match_sexec_si_all_ctx gm ibf1.(entry) ibf2.(entry) pc.
Proof.
  unfold aux_simu_check; wlp_simplify.
Qed.

Definition simu_check (RRULES: rrules_set) (f1 f2: function) (ibf1 ibf2: iblock_info)
  (gm: gluemap) (pc: node): res unit :=
  if has_returned (aux_simu_check RRULES f1 f2 ibf1 ibf2 gm pc)
  then OK tt
  else Error (msg "unexpected value of has_returned")
  .

Lemma simu_check_correct RRULES f1 f2 ibf1 ibf2 gm pc:
  simu_check RRULES f1 f2 ibf1 ibf2 gm pc = OK tt ->
  match_sexec_si_all_ctx gm ibf1.(entry) ibf2.(entry) pc.
Proof.
  unfold simu_check.
  destruct (has_returned _) eqn:Hres; try discriminate.
  intros _.
  exploit has_returned_correct; eauto.
  intros (r & X); eapply aux_simu_check_correct; eauto.
Qed.