(**************************************************************)
(*   Copyright Dominique Larchey-Wendling [*]                 *)
(*                                                            *)
(*                             [*] Affiliation LORIA -- CNRS  *)
(**************************************************************)
(*      This file is distributed under the terms of the       *)
(*         CeCILL v2 FREE SOFTWARE LICENSE AGREEMENT          *)
(**************************************************************)
(* Trying an abstract compiler with labels instead of nats as source code addressing *)
(** ** Semantic Correctness of Compiled Code *)
Set Implicit Arguments.
Section comp.
(** This is an abstract proof of compiler soundness & completeness  The principle of this compiler is to map every source individual instruction into a list of target instructions that simulate the source instruction. We describe our assumptions later on ...  *)
Variable (X Y : Set)                                  (* X is a small type of source instructions and
Y of destination instructions *)
(icomp : (nat -> nat) -> nat -> X -> list Y) (* instruction compiler w.r.t. a given linker & a position
icomp lnk i x compiles instruction x at position i
using linker lnk into a list of target instructions
*)
(ilen  : X -> nat)                           (* compiled code length does not depend on linker or position,
it only depends on the original instruction
whether this assumption is strong or not is debatable
but we only encountered cases which satisfy this assuption
*)
(Hilen : forall lnk n x, length (icomp lnk n x) = ilen x)
(*Hilen2  : forall x, 1 <= ilen x*).           (* compiled code should not be empty, even if the source instruction is something like NO-OP, to ensure progress in the simulation as source code executes also not a strong requirement  This can be removed because it can be deduced (where it is used) from Hilen1 & step_X_tot & Hicomp *)
(* Semantics for X and Y instructions *)
Variables (state_X state_Y : Type)
(step_X : X -> (nat*state_X) -> (nat*state_X) -> Prop)
(step_Y : Y -> (nat*state_Y) -> (nat*state_Y) -> Prop).
Notation "i '/X/' s -1> t" := (step_X i s t) (at level 70, no associativity).
Notation "P '/X/' s '-[' k ']->' t" := (sss_steps step_X P k s t) (at level 70, no associativity).
Notation "P '/X/' s '-+>' t" := (sss_progress step_X P s t) (at level 70, no associativity).
Notation "P '/X/' s ->> t" := (sss_compute step_X P s t) (at level 70, no associativity).
Notation "P '/X/' s '~~>' t" := (sss_output step_X P s t) (at level 70, no associativity).
Notation "P '/X/' s ↓" := (sss_terminates step_X P s)(at level 70, no associativity).
Notation "i '/Y/' s -1> t" := (step_Y i s t) (at level 70, no associativity).
Notation "P '/Y/' s '-[' k ']->' t" := (sss_steps step_Y P k s t) (at level 70, no associativity).
Notation "P '/Y/' s '-+>' t" := (sss_progress step_Y P s t) (at level 70, no associativity).
Notation "P '/Y/' s ->> t" := (sss_compute step_Y P s t) (at level 70, no associativity).
Notation "P '/Y/' s '~~>' t" := (sss_output step_Y P s t) (at level 70, no associativity).
Notation "P '/Y/' s ↓" := (sss_terminates step_Y P s)(at level 70, no associativity).
(** We assume totality of X semantics, i.e. no instruction can block the computation and functionality of Y semantics  Totality is not necessary achieved ... think of a HALT instruction what should we do in that case ? It should not be too difficult to embed a partial model of computation into a total one by transforming blocking cases into jumps at a PC value outside of the code. *)
Hypothesis (step_X_tot : forall I st1, exists st2, I /X/ st1 -1> st2)
(step_Y_fun : forall I st st1 st2, I /Y/ st -1> st1 -> I /Y/ st -1> st2 -> st1 = st2).
(** simul is an invariant: simul st_X st_Y means that st_X is simulated by st_Y *)
Variable (simul : state_X -> state_Y -> Prop).
Infix "⋈" := simul (at level 70, no associativity).
(** Simulation is preserved by compiled instructions this of course ensures the *semantic correctness of the compilation of individual instructions*  Notice the important hypothesis of preservation of the +1 relative address by the linker otherwise it might not be possible to establish the below predicate.  If the source language involves other relative addresses like +2 or +d or -d, the present compiler might have to be substantially updated.  +1 is very likely to be used even implicitly because every instruction that does not branch (like INC or PUSH) implicitly jumps at +1 ... *)
Definition instruction_compiler_sound := forall lnk I i1 v1 i2 v2 w1,
I /X/ (i1,v1) -1> (i2,v2)
-> lnk (1+i1) = length (icomp lnk i1 I) + lnk i1
-> v1 ⋈ w1
-> exists w2, (lnk i1,icomp lnk i1 I) /Y/ (lnk i1,w1) -+> (lnk i2,w2)
/\ v2 ⋈ w2.
Hypothesis Hicomp : instruction_compiler_sound.
Section correctness.
(** We assume each instruction in P is compiled in Q according to the individual instruction compiler combined with what the linker says for branching. This is a *syntactic correctness criterion* for the whole compiled program Q *)
Variables (linker : nat -> nat) (P : nat * list X) (Q : nat * list Y)
(HPQ : forall i I, (i,I::nil) <sc P -> (linker i, icomp linker i I) <sc Q
/\ linker (1+i) = ilen I + linker i).
(** From semantic correctness of individually compiled instructions and syntactic correctness of the whole compiled program, we derive soundness and completeness of the compiled program Q wrt the source program P *)

Theorem compiler_sound i1 v1 i2 v2 w1 :
v1 ⋈ w1 /\ P /X/ (i1,v1) ->> (i2,v2)
-> exists w2, v2 ⋈ w2 /\ Q /Y/ (linker i1,w1) ->> (linker i2,w2).