(**************************************************************)
(*   Copyright Dominique Larchey-Wendling *                 *)
(*                                                            *)
(*                             * Affiliation LORIA -- CNRS  *)
(**************************************************************)
(*      This file is distributed under the terms of the       *)
(*         CeCILL v2 FREE SOFTWARE LICENSE AGREEMENT          *)
(**************************************************************)

Require Import List Arith Lia.

From Undecidability.Shared.Libs.DLW
  Require Import Utils.utils Code.subcode.

Set Implicit Arguments.

Reserved Notation "ρ '//' r '-1>' s" (at level 70, no associativity).
Reserved Notation "P '//' r ':1>' s" (at level 70, no associativity).
Reserved Notation "P '//' r '-[' n ']->' s" (at level 70, no associativity, format "P // r -[ n ]-> s").
Reserved Notation "P '//' r '-+>' s" (at level 70, no associativity).
Reserved Notation "P '//' r '->>' s" (at level 70, no associativity).
Reserved Notation "P '//' r '-]]' s" (at level 70, no associativity).
Reserved Notation "P '//' r '~~>' s" (at level 70, no associativity).
Reserved Notation "P '//' r '~[' n ']~>' s" (at level 70, no associativity, format "P // r ~[ n ]~> s").
Reserved Notation "P '//' r ↓" (at level 70, no associativity).

(* Intented meaning of notations

  ρ // r -1> s :      execution of instruction ρ changes state r into state s
  P // r :1> s :      execution of ONE instruction in P changes state r into state s            (one step)
  P // r -n-> s :   execution of n instructions in P changes state r into state s             (n steps)
  P // r -+> s :      execution of at least one instruction in P changes state r into state s   (progress)
  P // r ->> s :      execution of some instructions in P changes state r into state s          (compute)
  P // r -]] s :      maximal execution in P  (not really used, ~~> prefered below)
  P // r ~~> s :      execution of some instructions in P until s is outside of P 
                           (stronger than -]] below if semantics is not total)                  (output)
  P // r ~n~> s :   execution of n instructions in P and then s is outside of P
  P // r ↓ :          termination on a state outside of P                                       
                           NOT identical to termination for non-total semantics                 (terminates)

  see ./compiler_correction.v for typical use of these notations
*)


Section Small_Step_Semantics.

  (* * Code
      A generic library to implement Small Step Semantics on programs described
      by lists of instructions that modify a state and a Program Counter (PC) 
     
      An important part of the library is devoted to show locality lemmas
      that related computation inside sub-programs to outer programs
      
      This is ESSENTIAL to be able to show properties compositionally,
      ie to derive properties of program from properties of sub-programs
   *)


  (* ** Syntax *)

  Variable (instr : Set) (data : Type).

  (* A state contain the PC (current instruction) and data *)

  Notation state := (nat * data)%type.

  Variable one_step : instr -> state -> state -> Prop.

  Notation "i // s -1> t" := (one_step i s t) (at level 70, no associativity).
  Notation "s ⟬ i ⦒ t" := (one_step i s t) (at level 70, no associativity).

  Hypothesis (sss_fun : forall i s t1 t2, i // s -1> t1 -> i // s -1> t2 -> t1 = t2).
  Hypothesis (sss_dec : forall i st1 st2, { i // st1 -1> st2 } + { ~ i // st1 -1> st2 }).

  (* A code is a contiguous list of instructions starting at some position *)

  Notation code := (nat * list instr)%type.

  (* ** Semantics *)

  (* This a one step computation *)

  Definition sss_step P st1 st2 := exists k l i r d, P = (k,l++i::r)
                                                  /\ st1 = (k+length l,d)
                                                  /\ i // st1 -1> st2.

  Notation "P // r :1> s" := (sss_step P r s) (at level 70, no associativity).

  (* Functionality of one step computations *)

  Fact sss_step_fun P s t1 t2 : P // s :1> t1 -> P // s :1> t2 -> t1 = t2.
  Proof.
    intros (k1 & l1 & i1 & r1 & d1 & ? & ? & H1)
           (k2 & l2 & i2 & r2 & d2 & ? & ? & H2).
    subst.
    inversion H3.
    inversion H4.
    subst k2.
    apply list_app_inj in H5; try lia.
    destruct H5 as (? & H5); inversion H5.
    subst.
    revert H1 H2; apply sss_fun.
  Qed.

  (* A "more general/usable" constructor *)

  Fact in_sss_step k l i r st1 st2 : fst st1 = k+length l
                               -> i // st1 -1> st2
                               -> (k,l++i::r) // st1 :1> st2.
  Proof. destruct st1 as (j,d); simpl; intro; subst; exists k,l,i,r,d; auto. Qed.

  (* A destructor *)

  Fact sss_step_subcode_inv P ii st st' :
        (fst st, ii::nil) <sc P -> P // st :1> st' -> ii // st -1> st'.
  Proof.
    intros H1 (k & l2 & jj & r2 & d1 & H3 & H4 & H5); subst P st.
    destruct H1 as (l1 & r1 & H1 & H2).
    simpl in H1, H2.
    apply list_app_inj in H1; try lia.
    destruct H1 as (? & H1); subst l2.
    inversion H1; subst jj r2; auto.
  Qed.

  Fact sss_step_supcode P Q st st' :
       P <sc Q -> in_code (fst st) P -> Q // st :1> st' -> P // st :1> st'.
  Proof.
    intros H1 H2 (k & l & ii & r & d & ? & ? & H3); subst.
    destruct P as (iP,cP).
    destruct H1 as (ll & rr & H1 & H4).
    simpl in H2.
    symmetry in H1.
    apply list_app_cons_eq_inv in H1.
    destruct H1 as [ (m & G1 & G2) | (m & G1 & G2) ].
    2: apply f_equal with (f := @length _) in G1;
       exfalso; revert G1; rew length; intro; lia.
    apply list_app_cons_eq_inv in G2.
    destruct G2 as [ (p & G3 & G4) | (p & G3 & G4) ].
    subst; exfalso; revert H2; rew length; intro; lia.
    subst.
    apply in_sss_step; auto.
    revert H2; rew length; simpl; intro; lia.
  Qed.

  (* sss_step is decidable *)

  Fact sss_step_dec P st1 st2 : { P // st1 :1> st2 } + { ~ P // st1 :1> st2 }.
  Proof.
    destruct st1 as (j,d).
    destruct P as (k,ll).
    destruct (le_lt_dec k j) as [ H1 | H1 ].
    + destruct (le_lt_dec (S j) (k+length ll)) as [ H2 | H2 ].
      * destruct (@list_pick _ ll (j-k)) as (i & l & r & ? & ?).
        - lia.
        - assert (j = k+length l) by lia; subst.
          destruct (sss_dec i (k+length l,d) st2) as [ | C ].
          ++ left; apply in_sss_step; auto.
          ++ right; contradict C.
             destruct C as (k1 & l1 & i1 & r1 & d1 & H3 & H4 & H5).
             inversion H3; subst.
             inversion H4; subst.
             apply list_app_inj in H7 as (-> & H7); try lia.
             inversion H7; subst; auto.
      * right; intros (k1 & l1 & i1 & r1 & d1 & H3 & H4 & H5).
        inversion H3; subst.
        inversion H4; subst.
        rewrite app_length in H2; simpl in H2; lia.
    + right; intros (k1 & l1 & i1 & r1 & d1 & H3 & H4 & H5).
      inversion H3; subst.
      inversion H4; subst.
      lia.
  Qed.

  (* This is a n steps computation *)

  Inductive sss_steps (P : code) : nat -> state -> state -> Prop :=
    | in_sss_steps_0 : forall st, P // st -[0]-> st
    | in_sss_steps_S : forall n st1 st2 st3, P // st1 :1> st2
                                              -> P // st2 -[n]-> st3
                                              -> P // st1 -[S n]-> st3
  where "P // r -[ n ]-> s" := (sss_steps P n r s).

  (* More usable constructors *)

  Fact sss_steps_0 P st1 st2 : st1 = st2 -> P // st1 -[0]-> st2.
  Proof. intros []; constructor. Qed.

  Fact sss_steps_1 P st1 st2 : P // st1 :1> st2 -> P // st1 -[1]-> st2.
  Proof.
    constructor 2 with st2; auto.
    constructor 1.
  Qed.

  Fact sss_steps_trans P n m st1 st2 st3 :
         P // st1 -[n]-> st2 -> P // st2 -[m]-> st3 -> P // st1 -[n+m]-> st3 .
  Proof.
    intros H; revert H st3.
    induction 1 as [ | ? ? st2 ]; simpl; intros; auto.
    constructor 2 with st2; auto.
  Qed.

  (* Inversion lemma *)

  Fact sss_steps_0_inv P st1 st2 : P // st1 -[0]-> st2 -> st1 = st2.
  Proof. inversion 1; auto. Qed.

  Fact sss_steps_S_inv P st1 st3 k :
                        st1 <> st3
      -> P // st1 -[k]-> st3
      -> exists k' st2, k = S k'
                     /\ P // st1 :1> st2
                     /\ P // st2 -[k']-> st3.
  Proof.
    intros H.
    inversion 1; subst.
    destruct H; auto.
    exists n, st2; auto.
  Qed.

  Fact sss_steps_inv P k st1 st3 :
          P // st1 -[k]-> st3
       -> (k = 0 /\ st1 = st3)
        + { k' | exists st2, k = S k'
                       /\ P // st1 :1> st2
                       /\ P // st2 -[k']-> st3 }%type.
  Proof.
    intros H.
    destruct k as [ | k ]; [ left | right ].
    apply sss_steps_0_inv in H; auto.
    exists k.
    inversion H; subst.
    exists st2; auto.
  Qed.

  Fact sss_steps_S_inv' P st1 st3 k :
                        P // st1 -[S k]-> st3
      -> exists st2, P // st1 :1> st2
                     /\ P // st2 -[k]-> st3.
  Proof.
    intros H.
    inversion H; subst.
    exists st2; auto.
  Qed.

  Fact sss_steps_fun P k s t1 t2 :
         P // s -[k]-> t1
      -> P // s -[k]-> t2
      -> t1 = t2.
  Proof.
    intros H; revert H t2; induction 1 as [ | ? ? st2 ? H1 H2 IH2 ]; simpl; intros t2 Ht2; auto.
    inversion Ht2; auto.
    inversion Ht2; subst.
    generalize (sss_step_fun H1 H0); intro; subst.
    apply IH2 in H3; auto.
  Qed.

  Fact sss_steps_plus_inv P n m st1 st3 :
         P // st1 -[n+m]-> st3
      -> exists st2, P // st1 -[n]-> st2
                  /\ P // st2 -[m]-> st3.
  Proof.
    revert st1 st3; induction n as [ | n IHn ]; intros s t.
    exists s; split; auto; constructor.
    inversion 1; subst.
    apply IHn in H2; destruct H2 as (st & ? & ?).
    exists st; split; auto.
    constructor 2 with st2; auto.
  Qed.

  Definition sss_progress P st1 st2 := exists k, 0 < k /\ P // st1 -[k]-> st2.
  Definition sss_compute P st1 st2 := exists k, P // st1 -[k]-> st2.

  Notation "P // r -+> s" := (sss_progress P r s).
  Notation "P // r ->> s" := (sss_compute P r s).

  Fact sss_progress_compute P st1 st2 : P // st1 -+> st2 -> P // st1 ->> st2.
  Proof. intros (k & _ & ?); exists k; auto. Qed.

  Fact sss_compute_trans P st1 st2 st3 : P // st1 ->> st2 -> P // st2 ->> st3 -> P // st1 ->> st3.
  Proof. intros (i & H1) (j & H2); exists (i+j); revert H1 H2; apply sss_steps_trans. Qed.

  Fact sss_progress_compute_trans P st1 st2 st3 : P // st1 -+> st2 -> P // st2 ->> st3 -> P // st1 -+> st3.
  Proof. intros (i & ? & H1) (j & H2); exists (i+j); split; try lia; revert H1 H2; apply sss_steps_trans. Qed.

  Fact sss_compute_progress_trans P st1 st2 st3 : P // st1 ->> st2 -> P // st2 -+> st3 -> P // st1 -+> st3.
  Proof. intros (i & H1) (j & ? & H2); exists (i+j); split; try lia; revert H1 H2; apply sss_steps_trans. Qed.

  Fact sss_progress_trans P st1 st2 st3 : P // st1 -+> st2 -> P // st2 -+> st3 -> P // st1 -+> st3.
  Proof. intro; apply sss_compute_progress_trans, sss_progress_compute; trivial. Qed.

  (* Computations must start inside code *)

  Fact sss_step_in_code P st1 st2 : P // st1 :1> st2 -> in_code (fst st1) P.
  Proof.
    revert P st1 st2; intros (n,c) (i,d) st2 (q & l & ii & r & d1 & H1 & H2 & _).
    inversion H1; subst.
    inversion H2; subst.
    simpl; rew length; lia.
  Qed.

  Fact sss_steps_compute P n st1 st2 : P // st1 -[n]-> st2 -> P // st1 ->> st2.
  Proof. exists n; trivial. Qed.

  (* We propose a library of locality lemmas that link computations
      in subcode to computations in the code *)


  (* ** Compisitional Reasoning over Subcode *)

  (* First inclusions lemmas *)

  Fact subcode_sss_step P Q st1 st2: P <sc Q -> P // st1 :1> st2 -> Q // st1 :1> st2.
  Proof.
    destruct P as (n1,c1).
    destruct Q as (n2,c2).
    simpl.
    intros (l & r & H1 & H2) H3.
    destruct H3 as (q & ll & i & rr & d1 & H3 & H4 & H5).
    inversion H3; subst.
    exists n2, (l++ll), i, (rr++r), d1; repeat split; auto.
    f_equal; solve list eq.
    f_equal; rew length; lia.
  Qed.

  Fact subcode_sss_steps P Q k st1 st2: P <sc Q -> P // st1 -[k]-> st2 -> Q // st1 -[k]-> st2.
  Proof.
    induction 2 as [ | k st1 st2 st3 H3 H4 IH4 ].
    constructor.
    constructor 2 with st2; auto.
    revert H3; apply subcode_sss_step; auto.
  Qed.

  Fact subcode_sss_progress P Q st1 st2: P <sc Q -> P // st1 -+> st2 -> Q // st1 -+> st2.
  Proof.
    intros ? (k & ? & Hk); exists k; split; auto; revert Hk; apply subcode_sss_steps; auto.
  Qed.

  Fact subcode_sss_compute P Q st1 st2: P <sc Q -> P // st1 ->> st2 -> Q // st1 ->> st2.
  Proof.
    intros ? (k & Hk); exists k; revert Hk; apply subcode_sss_steps; auto.
  Qed.

  Fact subcode_sss_compute_trans P Q st1 st2 st3 :
       P <sc Q -> P // st1 ->> st2 -> Q // st2 ->> st3 -> Q // st1 ->> st3.
  Proof.
    intros ? H.
    apply sss_compute_trans.
    revert H; apply subcode_sss_compute; auto.
  Qed.

  Fact subcode_sss_compute_linstr k li P st1 st2 st :
            (fst st1,li) // st1 -[k]-> st2
         -> (fst st1,li) <sc P
         -> P // st2 ->> st
         -> P // st1 ->> st.
  Proof.
    intros H1 H2.
    apply subcode_sss_compute_trans with (1 := H2).
    exists k; auto.
  Qed.

  Fact subcode_sss_compute_instr P i st1 st2 st3 :
            i // st1 -1> st2
        -> (fst st1,i::nil) <sc P
        -> P // st2 ->> st3
        -> P // st1 ->> st3.
  Proof.
    intro H; apply subcode_sss_compute_linstr with (k := 1).
    apply sss_steps_1, in_sss_step with (l := nil); auto.
  Qed.

  (* Then are reverse inclusion lemmas *)

  Fact subcode_sss_step_inv P Q st1 st2 :
         P <sc Q
      -> in_code (fst st1) P
      -> Q // st1 :1> st2
      -> P // st1 :1> st2.
  Proof.
    revert P Q st1 st2; intros (nP,cP) (nQ,cQ) (i,d) st; simpl.
    intros (l & r & H1 & H2) H3 (q & ll & ii & rr & d1 & H4 & H5 & H6).
    inversion H4; subst.
    inversion H5; subst.
    clear H4 H5.
    apply list_app_cons_eq_inv in H7.
    destruct H7 as [ (m & Hm1 & Hm2) | (m & Hm1 & Hm2) ].
    apply list_app_cons_eq_inv in Hm2.
    destruct Hm2 as [ (p & Hp1 & Hp2) | (p & Hp1 & Hp2) ].
    + apply f_equal with (f := @length _) in Hm1.
      apply f_equal with (f := @length _) in Hp1.
      revert Hm1 Hp1; rew length; intros; lia.
    + subst cP; apply in_sss_step; auto.
      simpl; subst; rew length; lia.
    + apply f_equal with (f := @length _) in Hm1.
      apply f_equal with (f := @length _) in Hm2.
      revert Hm1 Hm2; rew length; intros; lia.
  Qed.

  Definition sss_output P st st' := P // st ->> st' /\ out_code (fst st') P.

  Notation "P // x ~~> y" := (sss_output P x y).
  Notation "P // r ~[ n ]~> s" := (P // r -[n]-> s /\ out_code (fst s) P).

  Definition sss_terminates P st := exists st', P // st ~~> st'.

  Notation "P // x ↓" := (sss_terminates P x).

  Fact subcode_sss_terminates_instr P i st1 st2 :
            i // st1 -1> st2
        -> (fst st1,i::nil) <sc P
        -> P // st2
        -> P // st1 .
  Proof.
    intros H1 H2 (st3 & H3 & H4).
    exists st3; split; auto.
    revert H1 H2 H3; apply subcode_sss_compute_instr.
  Qed.

  (* No computation is possible *)

  Definition sss_stall ii st := forall st', ~ ii // st -1> st'.
  Definition sss_step_stall P st := forall st', ~ P // st :1> st'.

  Fact sss_steps_stall_inv P p s1 s2 : sss_step_stall P s1 -> P // s1 -[p]-> s2 -> p = 0 /\ s1 = s2.
  Proof.
    intros H; induction 1 as [ | p st1 st2 st3 H1 ]; auto.
    apply H in H1; destruct H1.
  Qed.

  Fact sss_steps_stall_fun P p q s1 s2 :
         sss_step_stall P s2
      -> P // s1 -[p]-> s2
      -> P // s1 -[q]-> s2
      -> p = q.
  Proof.
    intros H1 H2; revert H2 q.
    induction 1 as [ s1 | p s1 s2 s3 H2 H3 IH3 ];
      intros q H5.
    apply sss_steps_stall_inv, proj1 in H5; auto.
    destruct q.
    apply sss_steps_0_inv in H5; subst.
    apply H1 in H2; destruct H2.
    specialize (IH3 H1).
    apply sss_steps_S_inv' in H5.
    destruct H5 as (st2' & H5 & H6).
    generalize (sss_step_fun H2 H5).
    intros; subst st2'.
    apply IH3 in H6; subst; auto.
  Qed.

  Definition sss_compute_max P s1 s2 := (P // s1 ->> s2 /\ sss_step_stall P s2).

  Notation " P // s1 -]] s2 " := (sss_compute_max P s1 s2).

  (* No computation outside the code *)

  Fact sss_out_step_stall P st : out_code (fst st) P -> sss_step_stall P st.
  Proof.
    revert P st; intros (n,code) (i,st).
    intros H1 st' (k & l & ii & r & d1 & H2 & H3 & H4).
    inversion H2; inversion H3; subst.
    simpl in H1.
    revert H1; rew length; intros []; lia.
  Qed.

  Fact sss_stall_step_stall ii P st :
          (fst st,ii::nil) <sc P
       -> sss_stall ii st
       -> sss_step_stall P st.
  Proof.
    intros H1 H2 st' H.
    apply sss_step_subcode_inv with (1 := H1) in H.
    revert H; apply H2.
  Qed.

  Fact sss_stall_step_0 ii P q st st' :
          (fst st,ii::nil) <sc P
       -> sss_stall ii st
       -> P // st -[q]-> st' -> q = 0 /\ st = st'.
  Proof.
    intros H1 H2.
    generalize (sss_stall_step_stall H1 H2).
    intros H3 H4.
    apply sss_steps_inv in H4.
    destruct H4 as [ | (k & st2 & _ & H5 & _) ]; auto.
    contradict H5; apply H3.
  Qed.

  Fact sss_step_stall_inv P st :
          sss_step_stall P st
       -> { ii | (fst st,ii::nil) <sc P /\ sss_stall ii st }
        + { out_code (fst st) P }.
  Proof.
    intros H.
    destruct (in_out_code_dec (fst st) P) as [ H1 | H1 ].
    2: tauto.
    destruct P as (iP,cP).
    destruct st as (j,d).
    simpl in H1.
    destruct (@list_pick _ cP (j-iP)) as (ii & l & r & H2 & H3).
    lia.
    left; exists ii; simpl; split.
    exists l, r; split; auto; lia.
    intros st' H4.
    apply (H st').
    subst; apply in_sss_step; auto.
    simpl; lia.
  Qed.

  Fact sss_steps_stall k P st st' :
         out_code (fst st) P
      -> P // st -[k]-> st'
      -> k = 0 /\ st = st'.
  Proof.
    destruct P; simpl.
    intros H1 H2.
    apply sss_steps_inv in H2.
    destruct H2 as [ (? & ?) | (m & st2 & ? & H2 & _) ]; auto.
    apply sss_out_step_stall in H2; try red; tauto.
  Qed.

  (* This is a critical lemma about the locality of computations
  
      If P is subcode of Q and a computation in Q starts in P and ends outside of P,
      then it is decomposed in a first computation in P ending outside of P
      and a second computation in Q 
      
    *)


  Lemma subcode_sss_steps_inv P Q k st1 st3 :
       P <sc Q
    -> in_code (fst st1) P
    -> out_code (fst st3) P
    -> Q // st1 -[k]-> st3
    -> exists k1 k2 st2,
          P // st1 -[k1]-> st2
       /\ Q // st2 -[k2]-> st3
       /\ k = k1+k2
       /\ out_code (fst st2) P.
  Proof.
    intros H1 H2 H3 H4; revert H4 H2 H3.
    induction 1 as [ st1 | k st1 st2 st3 H4 H5 IH5 ]; intros H2 H3.

    exists 0, 0, st1; repeat split; auto; constructor.

    apply subcode_sss_step_inv with (1 := H1) in H4; auto.
    destruct (in_out_code_dec (fst st2) P) as [ H6 | H6 ].

    destruct (IH5 H6 H3) as (k1 & k2 & st & ? & ? & ? & ?).
    exists (S k1), k2, st; repeat split; auto.
    constructor 2 with (1 := H4); auto.
    lia.

    exists 1, k, st2; repeat split; auto.
    apply sss_steps_1; auto.
  Qed.

  Lemma subcode_sss_compute_inv P Q st1 st3 :
       P <sc Q
    -> out_code (fst st3) P
    -> Q // st1 ->> st3
    -> exists st2,
          P // st1 ->> st2
       /\ Q // st2 ->> st3
       /\ out_code (fst st2) P.
  Proof.
    intros H1 H3 (k & Hk).
    destruct (in_out_code_dec (fst st1) P) as [ H2 | H2 ].
    + apply subcode_sss_steps_inv with (1 := H1) in Hk; auto.
      destruct Hk as (k1 & k2 & st2 & H4 & H5 & H6 & H7).
      exists st2; repeat split; auto; red; firstorder.
    + exists st1; repeat split; auto.
      * exists 0; constructor.
      * red; firstorder.
  Qed.

  (* Inversion lemma *)

  Fact subcode_sss_step_inv_1 P i st1 st2 : (fst st1,i::nil) <sc P -> P // st1 :1> st2 -> i // st1 -1> st2.
  Proof.
    revert P st1; intros (j,cj) (n,d) H1 H2.
    apply subcode_sss_step_inv with (1 := H1) in H2.
    2: simpl; lia.
    destruct H2 as (q & l & ii & r & d1 & H2 & H3 & H4).
    inversion H3; subst n d1; auto.
    eq goal H4; f_equal.
    inversion H2.
    destruct l as [ | ? [ | ] ]; try discriminate.
    inversion H5; subst; auto.
  Qed.

  (* Starting a computation in subcode ending outside of it
     can be cut in half *)


  Fact subcode_sss_subcode_inv P Q p k st1 st2 st3 :
           out_code (fst st3) P
        -> P <sc Q
        -> P // st1 -[p]-> st2
        -> Q // st1 -[k]-> st3
        -> exists q, k = p+q /\ Q // st2 -[q]-> st3.
  Proof.
    revert P Q st3; intros P (j,cj) (n,d) H1 H2 H3.
    simpl in H1.
    revert k.
    induction H3 as [ st | p st1 st2 st3 H4 H5 IH5 ];
      intros k Hk.
    exists k; auto.
    apply sss_steps_inv in Hk.
    destruct Hk as [ (? & Hk) | (m & st4 & ? & Hk & H3) ].
    1: subst st1; apply sss_step_in_code in H4; simpl in H4; lia.
    apply subcode_sss_step with (1 := H2) in H4.
    rewrite (sss_step_fun Hk H4) in H3.
    apply IH5 in H3; auto.
    destruct H3 as (q & ? & H3).
    exists q; split; auto; lia.
  Qed.

  Fact subcode_sss_terminates_inv P Q st st1 :
           Q // st
        -> P <sc Q
        -> P // st ~~> st1
        -> Q // st1 .
  Proof.
    intros (st3 & (k & H1) & H2) H3 ((p & H4) & H5).
    destruct subcode_sss_subcode_inv with (2 := H3) (3 := H4) (4 := H1)
      as (q & _ & H6).
    1: revert H2; apply subcode_out_code; auto.
    exists st3; split; auto; exists q; auto.
  Qed.

  Fact subcode_sss_progress_inv P Q p st1 st2 st3 :
           out_code (fst st3) P
        -> P <sc Q
        -> P // st1 -+> st2
        -> Q // st1 -[p]-> st3
        -> exists q, q < p /\ Q // st2 -[q]-> st3.
  Proof.
    intros H1 H2 (k & ? & H3) H4.
    apply subcode_sss_subcode_inv with (3 := H3) in H4; auto.
    destruct H4 as (q & ? & ?); exists q; split; auto; lia.
  Qed.

  Section sss_terminates_ind.

    Variable (P : code) (R : state -> Prop).

    Hypothesis (HR0 : forall st, out_code (fst st) P -> R st)
               (HR1 : forall st1, (forall Q st2, Q <sc P -> Q // st1 -+> st2 -> P // st2 -> R st2) -> R st1).

    Theorem sss_terminates_ind st : P // st -> R st.
    Proof.
      intros (st2 & (k & H1) & H2); revert st st2 H1 H2.
      induction k as [ k IH ] using (well_founded_induction_type lt_wf).
      intros st1 st2 H1 H2.
      destruct k as [ | k ].
      + apply sss_steps_0_inv in H1.
        subst; apply HR0; auto.
      + apply HR1.
        intros Q st2' HQ H5 (st3 & H6 & H7).
        apply subcode_sss_progress_inv with (3 := H5) in H1; auto.
        destruct H1 as (q & G1 & G2).
        apply IH with (2 := G2); auto.
        revert H2; apply subcode_out_code; auto.
    Qed.

  End sss_terminates_ind.

  Section sss_compute_max_ind.

    Variable (P : code) (R : state -> state -> Prop).

    Hypothesis (HQ0 : forall st, sss_step_stall P st -> R st st)
               (HQ1 : forall st1 st3, (forall Q st2, Q <sc P -> Q // st1 -+> st2 -> P // st2 -]] st3 -> R st2 st3) -> R st1 st3).

    Theorem sss_compute_max_ind st1 st3 : P // st1 -]] st3 -> R st1 st3.
    Proof.
      intros ((k & H1) & H2); revert st1 H1.
      induction k as [ k IH ] using (well_founded_induction_type lt_wf).
      intros st1 H1.
      destruct k as [ | k ].
      apply sss_steps_0_inv in H1.
      subst; apply HQ0; auto.
      apply HQ1.
      intros Q st2' HQ H5 ((q & H6) & _).
      apply subcode_sss_progress with (1 := HQ) in H5.
      destruct H5 as (l & Hl & H5).
      generalize (sss_steps_trans H5 H6); intro H7.
      generalize (sss_steps_stall_fun H2 H1 H7); intros H8.
      apply IH with q; auto; lia.
    Qed.

  End sss_compute_max_ind.

  Hint Resolve subcode_refl : core.

  Fact sss_compute_inv P st1 st2 st3 :
             out_code (fst st3) P
          -> P // st1 ->> st2
          -> P // st1 ->> st3
          -> P // st2 ->> st3.
  Proof.
    intros H2 (p & H3) (k & H4).
    apply subcode_sss_subcode_inv with (3 := H3) in H4; auto.
    destruct H4 as (q & _ & ?); exists q; auto.
  Qed.

  Fact sss_compute_step_out_inv P k st1 st2 st3 :
           st1 <> st2
        -> out_code (fst st3) P
        -> P // st1 ->> st2
        -> P // st1 -[k]-> st3
        -> exists q, q < k /\ P // st2 -[q]-> st3.
  Proof.
    intros H1 H2 (p & H3) H4.
    apply subcode_sss_subcode_inv with (3 := H3) in H4; auto.
    destruct H4 as (q & H4 & H5).
    exists q; split; auto.
    destruct p; try lia.
    apply sss_steps_0_inv in H3.
    destruct H1; auto.
  Qed.

  Fact subcode_sss_subcode_compute_inv P Q k st1 st2 st3 :
           in_code (fst st1) P
        -> out_code (fst st2) P
        -> out_code (fst st3) P
        -> P <sc Q
        -> P // st1 ->> st2
        -> Q // st1 -[k]-> st3
        -> exists q, q < k /\ Q // st2 -[q]-> st3.
  Proof.
    intros H H0 H1 H2 (p & H3) H4.
    apply subcode_sss_subcode_inv with (2 := H2) (3 := H3) in H4; auto.
    destruct H4 as (q & Hq & H4).
    exists q; split; auto.
    destruct p; try lia.
    apply sss_steps_0_inv in H3; subst st2.
    red in H, H0; lia.
  Qed.

  Fact subcode_sss_steps_inv_1 P i k st1 st2 st3 :
        st1 <> st3
     -> i // st1 -1> st2
     -> (fst st1,i::nil) <sc P
     -> P // st1 -[k]-> st3
     -> exists k', k = S k' /\ P // st2 -[k']-> st3.
  Proof.
    intros H1 H2 H3 H4.
    apply sss_steps_inv in H4.
    destruct H4 as [ (_ & H4) | (k' & st & ? & H4 & ?) ].
    contradict H1; auto.
    exists k'; split; auto.
    apply subcode_sss_step_inv_1 with (1 := H3) in H4.
    rewrite (sss_fun H2 H4); auto.
  Qed.

  Fact subcode_sss_steps_stop P i k st1 st2 :
            (forall st, ~ i // st1 -1> st)
        -> (fst st1,i::nil) <sc P
        -> P // st1 -[k]-> st2 -> k = 0 /\ st1 = st2.
  Proof.
    intros H1 H2 H3.
    apply sss_steps_inv in H3.
    destruct H3 as [ | (k' & st & ? & H3 & H4) ]; auto; exfalso.
    apply (H1 st), subcode_sss_step_inv_1 with (1 := H2); auto.
  Qed.

  Fact sss_steps_stop P k st1 st2 :
             out_code (fst st1) P
          -> P // st1 -[k]-> st2
          -> st1 = st2.
  Proof.
    intros H1 H2.
    apply sss_steps_inv in H2.
    destruct H2 as [ [ _ ? ] | (k' & st3 & H2 & H3 & H4) ]; auto.
    destruct (sss_out_step_stall H1 H3).
  Qed.

  Fact sss_compute_stop P st1 st2 :
             out_code (fst st1) P
          -> P // st1 ->> st2
          -> st1 = st2.
  Proof.
    intros H1 (k & H2); revert H2; apply sss_steps_stop; auto.
  Qed.

  Fact sss_steps_stop_fun P a b st1 st2 st3 :
             out_code (fst st2) P
          -> out_code (fst st3) P
          -> P // st1 -[a]-> st2
          -> P // st1 -[b]-> st3
          -> a = b /\ st2 = st3.
  Proof.
    intros H1 H2 H3 H4.
    assert (a = b) as Hab.
    { destruct subcode_sss_subcode_inv with (3 := H3) (4 := H4)
        as (u & Hu & _); auto.
      destruct subcode_sss_subcode_inv with (3 := H4) (4 := H3)
        as (v & Hv & _); auto.
      lia. }
    split; auto; subst b.
    revert H3 H4; apply sss_steps_fun.
  Qed.

  Fact sss_compute_fun P st1 st2 st3 :
             out_code (fst st2) P
          -> out_code (fst st3) P
          -> P // st1 ->> st2
          -> P // st1 ->> st3
          -> st2 = st3.
  Proof.
    intros ? ? (a & H1) (b & H2).
    revert H1 H2; apply sss_steps_stop_fun; auto.
  Qed.

  Fact sss_steps_output_fun P a b st1 st2 st3 :
             P // st1 ~[a]~> st2
          -> P // st1 ~[b]~> st3
          -> a = b /\ st2 = st3.
  Proof.
    intros (H1 & H2) (H3 & H4).
    revert H1 H3; apply sss_steps_stop_fun; auto.
  Qed.

  Fact sss_output_fun P st st1 st2 : P // st ~~> st1 -> P // st ~~> st2 -> st1 = st2.
  Proof.
    intros (H1 & H2) (H3 & H4); revert H2 H4 H1 H3; apply sss_compute_fun.
  Qed.

  Fact subcode_sss_terminates P Q st : P <sc Q -> Q // st -> P // st .
  Proof.
    intros H (st' & H1 & H2).
    apply subcode_sss_compute_inv with (1 := H) in H1; auto.
    + destruct H1 as (st2 & H1 & H3 & H4).
      exists st2; split; auto.
    + revert H2; apply subcode_out_code; auto.
  Qed.

  Fact sss_progress_non_termination P st : P // st -+> st -> ~ P // st .
  Proof.
    intros (a & H1 & H2) (st' & (b & H3) & H4).
    assert (P // st -[a+b]-> st') as H5.
    { apply sss_steps_trans with (1 := H2); auto. }
    destruct sss_steps_stop_fun with (3 := H3) (4 := H5); auto; lia.
  Qed.

  Section sss_loop.

    (* P contains a loop that modifies the data (in a way computable by f)
       when condition C1 is satisfied and otherwise stops somewhere outside
       of P when condition C2 is satisfied.

       Then P stops iff exists n, C2 (iter f x n) *)


    Variable (P : code) (pre : data -> Prop) (spec : data -> data -> Prop)
             (f : data -> data) (Hf : forall x, x <> f x)
             (C1 C2 : data -> Prop) (HC : forall x, pre x -> { C1 x } + { C2 x })
             (i : nat) (p : nat) (Hp : out_code p P)
             (HP1 : forall x, pre x -> C1 x -> P // (i,x) ->> (i,f x) /\ pre (f x))
             (HP2 : forall x, pre x -> C2 x -> exists y, P // (i,x) ->> (p,y) /\ spec x y).

    Theorem sss_loop_sound x : pre x
                            -> (exists n, C2 (iter f x n))
                            -> exists n y, P // (i,x) ->> (p,y) /\ spec (iter f x n) y.
    Proof.
      intros H (n & Hn); revert x H Hn.
      induction n as [ | n IHn ]; intros x Hx Hn.
      simpl; exists 0; apply HP2; auto.
      destruct (HC Hx) as [ H | H ].
      apply IHn in Hn.
      destruct Hn as (m & y & H1 & H2).
      exists (S m), y; split; auto.
      generalize H1; apply sss_compute_trans.
      apply HP1; auto.
      apply HP1; auto.
      destruct (HP2 Hx) as (y & H1 & H2); auto.
      exists 0, y; auto.
    Qed.

    Theorem sss_loop_complete x y q : pre x
                                   -> out_code q P
                                   -> P // (i,x) ->> (q,y)
                                   -> p = q /\ exists n, C2 (iter f x n) /\ spec (iter f x n) y.
    Proof.
      intros Hx Hq (k & Hk); revert x Hx y q Hq Hk.
      induction k as [ k IHk ] using (well_founded_induction lt_wf); intros x Hx y q Hq Hk.
      destruct (HC Hx) as [ H | H ].
      * apply HP1 in H; auto; destruct H as [ H1 H2 ].
        destruct (@sss_compute_step_out_inv P k (i,x) (i,f x) (q,y))
          as (k' & H3 & H4); auto.
        { inversion 1 as [ H3 ]; revert H3; apply Hf. }
        destruct IHk with (2 := H2) (4 := H4) as (E & n & Hn); auto.
        split; auto; exists (S n); auto.
      * destruct HP2 with (2 := H) as (z & Hz & Hy); auto.
        apply ex_intro with (x := k) in Hk.
        generalize (@sss_compute_fun _ _ (p,z) (q,y) Hp Hq Hz Hk).
        inversion 1; subst; auto.
        split; auto.
        exists 0; simpl; auto.
    Qed.

  End sss_loop.

End Small_Step_Semantics.

(*
Notation " P '//' r ':1>' s " := (sss_step _ P r s)  (at level 70, no associativity).
Notation " P '//' r '-' n '->' s " := (sss_steps _ P n r s) (at level 70, no associativity).
Notation " P '//' r '->>' s " := (sss_compute _ P r s) (at level 70, no associativity).
*)