Theory Default_St_Transfer

theory Default_St_Transfer
  imports Default_St_Algebra "Voblint_Framework.Transfer_Algebra"
begin

unbundle default_st_carrier_syntax

section ‹Refinement to variable-indexed states›

text ‹
  Everything so far is stated about locations. A program, though, talks about
  variable names, and which names are global is a property of the program, not
  of the carrier. ‹location_of› is that classifier applied: it turns a name
  into the location holding its value, and ‹default_st_rep_to_fun› maps a
  whole state to the function typ‹'a abs_state› it represents, the form
  soundness is stated over.

  The rest of the theory is the operations a call and a return need --
  assignment, formal binding, restriction to one side of the ownership split,
  frame entry, and the caller/callee combination -- each paired with the
  equation saying that performing it on the carrier and then taking the
  represented function agrees with performing its counterpart on that
  function. Those equations are the interface every soundness proof downstream
  actually uses.
›

subsection ‹Location classification and projection›

definition location_of ::
  "(vname => bool) => vname => location" where
  "location_of 𝒢 x =
     (if 𝒢 x then Global_Location x else Local_Location x)"

text ‹
  Both tags carry the vname they classify, so a classified location determines
  its own name and two names collide only when they are equal.  Proofs about
  const‹location_of› therefore need no case split on term‹𝒢 x› against
  term‹𝒢 y›.
›

lemma location_vname_location_of [simp]:
  "location_vname (location_of 𝒢 x) = x"
  by (simp add: location_of_def)

lemma location_of_eq_iff [simp]:
  "location_of 𝒢 x = location_of 𝒢 y ⟷ x = y"
  by (auto simp: location_of_def)

text ‹
  Deliberately no ‹[simp]› rule matching term‹location_of 𝒢 x = Local_Location y›
  against a constructor. Such a rule reads well in isolation, but proofs here
  derive facts of exactly that shape and then use them as rewrites for
  const‹location_of›; a simp rule on the same left-hand side collapses the
  equation to its classification side and takes the rewrite away.
›

definition default_st_rep_to_fun ::
  "(vname => bool) => ('a::bot) default_st_rep => vname => 'a" where
  "default_st_rep_to_fun 𝒢 s x =
     default_st_rep_get s (location_of 𝒢 x)"

lemma default_st_rep_to_fun_set_location [simp]:
  "default_st_rep_to_fun 𝒢
      (default_st_rep_set s (location_of 𝒢 x) a) =
   (default_st_rep_to_fun 𝒢 s)(x := a)"
proof (rule ext)
  fix y
  show "default_st_rep_to_fun 𝒢
      (default_st_rep_set s (location_of 𝒢 x) a) y =
    ((default_st_rep_to_fun 𝒢 s)(x := a)) y"
    unfolding default_st_rep_to_fun_def    by (cases "x = y"; cases "𝒢 x"; cases "𝒢 y";
        simp_all add: location_of_def)
qed


definition default_st_to_fun ::
  "(vname => bool) => ('a::bot) default_st => vname => 'a"
where
  "default_st_to_fun 𝒢 s x =
     s⟨location_of 𝒢 x⟩"

text ‹
  The function a state represents is its const‹readback›, written
  ‹ρ𝒢 s›; a lifted state reads back pointwise under the lift.
  The notation joins the carrier notation in the opt-in bundle
  ‹default_st_syntax›: vendored theories bind ‹ρ› as a
  variable, so it must never be global.
›

adhoc_overloading readback == default_st_to_fun

bundle default_st_syntax
begin
unbundle default_st_carrier_syntax
notation readback ("ρ⇘_⇙")
end

unbundle default_st_syntax

lemma default_st_to_fun_bot [simp]:
  "ρ⇘𝒢⇙ (bot :: ('a::order_bot) default_st) = bot"
  by (rule ext) (simp add: default_st_to_fun_def)

text ‹
  The state a run starts in gives every local one value and every global
  another: C initializes a declared global to zero and leaves a local
  unconstrained. Each domain's C-initial state is this construction at its own
  abstraction of zero and its whole-value element, so the equation for its
  function is one lemma rather than one per domain. The two empty dictionaries
  are abstracted directly rather than lifted, because a domain whose value type
  is itself a typedef would otherwise descend through both quotients.
›

definition initial_default_st :: "'a::bot => 'a => 'a default_st" where
  "initial_default_st local_value global_value =
     ⟪(local_value, []), (global_value, [])⟫"

lemma default_st_get_initial [simp]:
  "ρ⇘𝒢⇙ (initial_default_st local_value global_value) x =
   (if 𝒢 x then global_value else local_value)"
  unfolding default_st_to_fun_def initial_default_st_def
  by (auto simp: location_of_def split: if_splits)

lemma default_st_to_fun_initial:
  "ρ⇘𝒢⇙ (initial_default_st local_value global_value) =
   (λx. if 𝒢 x then global_value else local_value)"
  by (rule ext) simp

text ‹
  The quotient-level projection agrees with the raw one at ‹s›'s own chosen
  representative -- so any raw-level fact about @{const default_st_rep_to_fun}
  transports directly to @{const default_st_to_fun} through
  @{const rep_default_st}, with no separate quotient-respectfulness argument
  needed: @{const rep_default_st} is a genuine function of ‹s›, so anything
  defined through it is automatically well-defined on the quotient.
›

lemma default_st_to_fun_rep:
  "ρ⇘𝒢⇙ s = default_st_rep_to_fun 𝒢 (rep_default_st s)"
  unfolding default_st_to_fun_def default_st_rep_to_fun_def
  by (rule ext) (simp add: default_st_get_rep)

lemma default_st_to_fun_mono:
  assumes "s ≤ t"
  shows "ρ⇘𝒢⇙ s ≤ ρ⇘𝒢⇙ t"
  using assms
  unfolding default_st_to_fun_def le_fun_def
  by (simp add: le_default_st_iff)


text ‹
  Each quotient-level refinement equation is its raw counterpart transported
  along the quotient: unfolding the projection leaves a goal in
  const‹default_st_get›, ‹transfer› lowers it to
  const‹default_st_rep_get›, and the raw lemma -- read at the same unfolded
  shape -- closes it. An operation returning the quotient has no ‹rep_eq› to
  unfold instead, since its representative is fixed only up to
  const‹eq_default_st_rep›.
›


subsection ‹Assignment and formal binding›

definition combine_assign_default_st_rep ::
  "(vname => bool) => vname option => 'a => ('a::bot) default_st_rep
   => 'a default_st_rep"
where
  "combine_assign_default_st_rep 𝒢 dst v s =
     (case dst of None => s
      | Some x => default_st_rep_set s (location_of 𝒢 x) v)"

lemma eq_default_st_rep_combine_assign:
  assumes "eq_default_st_rep s t"
  shows "eq_default_st_rep (combine_assign_default_st_rep 𝒢 dst v s)
      (combine_assign_default_st_rep 𝒢 dst v t)"
  by (unfold combine_assign_default_st_rep_def; cases dst;
      simp_all add: assms eq_default_st_rep_set)

lift_definition combine_assign_default_st ::
  "(vname => bool) => vname option => 'a => ('a::bot) default_st
   => 'a default_st"
  is combine_assign_default_st_rep
  by (rule eq_default_st_rep_combine_assign)

lemma default_st_get_combine_assign [simp]:
  "(combine_assign_default_st 𝒢 dst v s)⟨loc⟩ =
     (case dst of
        None => s⟨loc⟩
      | Some x => if location_of 𝒢 x = loc then v else s⟨loc⟩)"
  by transfer (auto simp add:combine_assign_default_st_rep_def split:option.splits)

lemma default_st_rep_to_fun_combine_assign [simp]:
  "default_st_rep_to_fun 𝒢
      (combine_assign_default_st_rep 𝒢 dst v s) =
   combine_assign dst v (default_st_rep_to_fun 𝒢 s)"
  by (cases dst)
     (simp_all add: combine_assign_default_st_rep_def)

lemma default_st_to_fun_combine_assign [simp]:
  "ρ⇘𝒢⇙
      (combine_assign_default_st 𝒢 dst v s) =
   combine_assign dst v (ρ⇘𝒢⇙ s)"
  unfolding default_st_to_fun_def
  by transfer
     (rule default_st_rep_to_fun_combine_assign[unfolded default_st_rep_to_fun_def])

definition bind_formals_default_st_rep ::
  "(vname => bool) => vname list => 'a list => ('a::bot) default_st_rep
   => 'a default_st_rep"
where
  "bind_formals_default_st_rep 𝒢 xs avs s =
     fold (λ(x, a) t. default_st_rep_set t (location_of 𝒢 x) a)
       (zip xs avs) s"

lemma eq_default_st_rep_fold_set:
  "eq_default_st_rep s t ⟹
     eq_default_st_rep
       (fold (λ(x, a) t. default_st_rep_set t (location_of 𝒢 x) a) ps s)
       (fold (λ(x, a) t. default_st_rep_set t (location_of 𝒢 x) a) ps t)"
proof (induction ps arbitrary: s t)
  case Nil
  then show ?case by simp
next
  case (Cons p ps)
  then show ?case
    by (cases p) (simp_all add: eq_default_st_rep_set)
qed

lemma eq_default_st_rep_bind_formals:
  assumes "eq_default_st_rep s t"
  shows "eq_default_st_rep (bind_formals_default_st_rep 𝒢 xs avs s)
      (bind_formals_default_st_rep 𝒢 xs avs t)"
  unfolding bind_formals_default_st_rep_def
  using assms by (rule eq_default_st_rep_fold_set)

lift_definition bind_formals_default_st ::
  "(vname => bool) => vname list => 'a list => ('a::bot) default_st
   => 'a default_st"
  is bind_formals_default_st_rep
  by (rule eq_default_st_rep_bind_formals)

text ‹A single-formal call binds exactly one location, so its reduction is a
  plain const‹default_st_set›. An instance with a
  one-argument procedure call cites this directly instead of unfolding
  const‹bind_formals_default_st›'s fold.›

lemma bind_formals_default_st_singleton:
  "bind_formals_default_st 𝒢 [x] [a] s = s⟨location_of 𝒢 x := a⟩"
  by transfer (simp add: bind_formals_default_st_rep_def eq_default_st_rep_def)

lemma default_st_rep_to_fun_fold_set:
  "default_st_rep_to_fun 𝒢
      (fold (λ(x, a) t. default_st_rep_set t (location_of 𝒢 x) a) ps s) =
   fold (λ(x, a) t. t(x := a)) ps
      (default_st_rep_to_fun 𝒢 s)"
  by (induction ps arbitrary: s)
     (simp_all split: prod.splits)

lemma default_st_rep_to_fun_bind_formals [simp]:
  "default_st_rep_to_fun 𝒢
      (bind_formals_default_st_rep 𝒢 xs avs s) =
   bind_formals xs avs (default_st_rep_to_fun 𝒢 s)"
  unfolding bind_formals_default_st_rep_def
  by (rule default_st_rep_to_fun_fold_set)

lemma default_st_to_fun_bind_formals [simp]:
  "ρ⇘𝒢⇙
      (bind_formals_default_st 𝒢 xs avs s) =
   bind_formals xs avs (ρ⇘𝒢⇙ s)"
  unfolding default_st_to_fun_def
  by transfer
     (rule default_st_rep_to_fun_bind_formals[unfolded default_st_rep_to_fun_def])

lemma default_st_to_fun_set [simp]:
  "ρ⇘𝒢⇙ s⟨location_of 𝒢 x := a⟩ =
   (ρ⇘𝒢⇙ s)(x := a)"
proof (rule ext)
  fix y
  show "ρ⇘𝒢⇙ s⟨location_of 𝒢 x := a⟩ y =
    ((ρ⇘𝒢⇙ s)(x := a)) y"
    unfolding default_st_to_fun_def
    by (cases "x = y"; cases "𝒢 x"; cases "𝒢 y";
        simp_all add: location_of_def)
qed


subsection ‹Ownership restriction and combination›

text ‹
  Each dictionary holds one partition, so the ownership operations below move
  whole dictionaries: a restriction keeps one and empties the other to
  ‹bot›, and the combination takes the caller's local dictionary and the
  callee's global one.
›

definition restrict_local_default_st_rep ::
  "('a::bot) default_st_rep => 'a default_st_rep" where
  "restrict_local_default_st_rep s = (fst s, (bot, []))"

definition restrict_global_default_st_rep ::
  "('a::bot) default_st_rep => 'a default_st_rep" where
  "restrict_global_default_st_rep s = ((bot, []), snd s)"


lemma default_st_rep_get_restrict_local:
  "default_st_rep_get (restrict_local_default_st_rep s) loc =
     (case loc of
        Local_Location x => default_st_rep_get s loc
      | Global_Location x => bot)"
  by (cases s; cases loc) (simp_all add: restrict_local_default_st_rep_def)

lemma default_st_rep_get_restrict_global:
  "default_st_rep_get (restrict_global_default_st_rep s) loc =
     (case loc of
        Local_Location x => bot
      | Global_Location x => default_st_rep_get s loc)"
  by (cases s; cases loc) (simp_all add: restrict_global_default_st_rep_def)

lemma eq_default_st_rep_restrict_local:
  assumes "eq_default_st_rep s t"
  shows "eq_default_st_rep (restrict_local_default_st_rep s)
      (restrict_local_default_st_rep t)"
  by (rule eq_default_st_repI)
     (simp add: default_st_rep_get_restrict_local eq_default_st_repD[OF assms]
       split: location.split)

lemma eq_default_st_rep_restrict_global:
  assumes "eq_default_st_rep s t"
  shows "eq_default_st_rep (restrict_global_default_st_rep s)
      (restrict_global_default_st_rep t)"
  by (rule eq_default_st_repI)
     (simp add: default_st_rep_get_restrict_global eq_default_st_repD[OF assms]
       split: location.split)

lift_definition restrict_local_default_st ::
  "('a::bot) default_st => 'a default_st"
  is restrict_local_default_st_rep
  by (rule eq_default_st_rep_restrict_local)

lift_definition restrict_global_default_st ::
  "('a::bot) default_st => 'a default_st"
  is restrict_global_default_st_rep
  by (rule eq_default_st_rep_restrict_global)

text ‹
  const‹restrict_local_default_st›/const‹restrict_global_default_st›
  preserve the caller's semantic default over the (potentially infinite)
  location space: the kept side carries over its input's own dictionary
  verbatim, whose default need not be term‹bot›, and only the dropped side
  is replaced by the empty dictionary over term‹bot›. A scope-parametric
  projection over a bounded materialized support would disagree with this
  pair outside its bound, so the pair is stated over the full location
  space and preserves the default by construction.
›

lemma default_st_get_restrict_local [simp]:
  "(restrict_local_default_st s)⟨loc⟩ =
     (case loc of
        Local_Location x => s⟨loc⟩
      | Global_Location x => bot)"
  by transfer (rule default_st_rep_get_restrict_local)

lemma default_st_get_restrict_global [simp]:
  "(restrict_global_default_st s)⟨loc⟩ =
     (case loc of
        Local_Location x => bot
      | Global_Location x => s⟨loc⟩)"
  by transfer (rule default_st_rep_get_restrict_global)

lemma default_st_to_fun_restrict_local [simp]:
  "ρ⇘𝒢⇙ (restrict_local_default_st s) x =
     (if 𝒢 x then bot else ρ⇘𝒢⇙ s x)"
  unfolding default_st_to_fun_def location_of_def
  by (cases "𝒢 x") simp_all

lemma default_st_to_fun_restrict_global [simp]:
  "ρ⇘𝒢⇙ (restrict_global_default_st s) x =
     (if 𝒢 x then ρ⇘𝒢⇙ s x else bot)"
  unfolding default_st_to_fun_def location_of_def
  by (cases "𝒢 x") simp_all

definition combine_default_st_rep ::
  "('a::bot) default_st_rep => 'a default_st_rep => 'a default_st_rep"
where
  "combine_default_st_rep sc se = (fst sc, snd se)"

lemma default_st_rep_get_combine [simp]:
  "default_st_rep_get (combine_default_st_rep sc se) loc =
   (case loc of
      Local_Location x => default_st_rep_get sc loc
    | Global_Location x => default_st_rep_get se loc)"
  by (cases sc; cases se; cases loc) (simp_all add: combine_default_st_rep_def)

lemma eq_default_st_rep_combine:
  assumes "eq_default_st_rep sc1 sc2"
    and "eq_default_st_rep se1 se2"
  shows "eq_default_st_rep (combine_default_st_rep sc1 se1)
      (combine_default_st_rep sc2 se2)"
  by (rule eq_default_st_repI)
     (simp add: eq_default_st_repD[OF assms(1)] eq_default_st_repD[OF assms(2)]
       split: location.split)

lift_definition combine_default_st ::
  "('a::bot) default_st => 'a default_st => 'a default_st"
  is combine_default_st_rep
  by (rule eq_default_st_rep_combine)

lemma default_st_get_combine [simp]:
  "(combine_default_st sc se)⟨loc⟩ =
     (case loc of
        Local_Location x => sc⟨loc⟩
      | Global_Location x => se⟨loc⟩)"
  by transfer (rule default_st_rep_get_combine)


lemma default_st_rep_to_fun_combine [simp]:
  "default_st_rep_to_fun 𝒢 (combine_default_st_rep sc se) =
   combine_env 𝒢 (default_st_rep_to_fun 𝒢 sc)
     (default_st_rep_to_fun 𝒢 se)"
proof (rule ext)
  fix x
  show "default_st_rep_to_fun 𝒢 (combine_default_st_rep sc se) x =
      combine_env 𝒢 (default_st_rep_to_fun 𝒢 sc)
        (default_st_rep_to_fun 𝒢 se) x"
    unfolding default_st_rep_to_fun_def combine_env_def location_of_def
    by (cases "𝒢 x") simp_all
qed

lemma default_st_to_fun_combine [simp]:
  "ρ⇘𝒢⇙ (combine_default_st sc se) =
   combine_env 𝒢 (ρ⇘𝒢⇙ sc)
     (ρ⇘𝒢⇙ se)"
proof (rule ext)
  fix x
  show "ρ⇘𝒢⇙ (combine_default_st sc se) x =
      combine_env 𝒢 (ρ⇘𝒢⇙ sc)
        (ρ⇘𝒢⇙ se) x"
    unfolding default_st_to_fun_def combine_env_def location_of_def
    by (cases "𝒢 x") simp_all
qed


lemma default_st_to_fun_sup [simp]:
  "ρ⇘𝒢⇙ (s ⊔ t) =
   ρ⇘𝒢⇙ s ⊔ ρ⇘𝒢⇙ t"
  by (rule ext) (simp add: default_st_to_fun_def sup_fun_def)


subsection ‹Frame entry›

text ‹
  Entering a frame sets every local to ‹top_val› and keeps the globals;
  the lemmas carry this through the quotient and the readback.
›

definition enter_frame_D_default_st_rep ::
  "'a => ('a::bot) default_st_rep => 'a default_st_rep"
where
  "enter_frame_D_default_st_rep top_val s = ((top_val, []), snd s)"

lemma default_st_rep_get_enter_frame_D [simp]:
  "default_st_rep_get (enter_frame_D_default_st_rep top_val s) loc =
   (case loc of
      Local_Location x => top_val
    | Global_Location x => default_st_rep_get s loc)"
  by (cases s; cases loc) (simp_all add: enter_frame_D_default_st_rep_def)

lemma eq_default_st_rep_enter_frame_D:
  assumes "eq_default_st_rep s t"
  shows "eq_default_st_rep (enter_frame_D_default_st_rep top_val s)
      (enter_frame_D_default_st_rep top_val t)"
  by (rule eq_default_st_repI)
     (simp add: eq_default_st_repD[OF assms] split: location.split)

lift_definition enter_frame_D_default_st ::
  "'a => ('a::bot) default_st => 'a default_st"
  is enter_frame_D_default_st_rep
  by (rule eq_default_st_rep_enter_frame_D)

lemma default_st_get_enter_frame_D [simp]:
  "(enter_frame_D_default_st top_val s)⟨loc⟩ =
     (case loc of
        Local_Location x => top_val
      | Global_Location x => s⟨loc⟩)"
  by transfer (rule default_st_rep_get_enter_frame_D)

lemma default_st_rep_to_fun_enter_frame [simp]:
  "default_st_rep_to_fun 𝒢 (enter_frame_D_default_st_rep top_val s) =
   enter_frame 𝒢 top_val (default_st_rep_to_fun 𝒢 s)"
proof (rule ext)
  fix x
  show "default_st_rep_to_fun 𝒢 (enter_frame_D_default_st_rep top_val s) x =
      enter_frame 𝒢 top_val (default_st_rep_to_fun 𝒢 s) x"
    unfolding enter_frame_def default_st_rep_to_fun_def location_of_def
    by (cases "𝒢 x") simp_all
qed

lemma default_st_to_fun_enter_frame [simp]:
  "ρ⇘𝒢⇙
      (enter_frame_D_default_st top_val s) =
   enter_frame 𝒢 top_val (ρ⇘𝒢⇙ s)"
  unfolding default_st_to_fun_def
  by transfer
     (rule default_st_rep_to_fun_enter_frame[unfolded default_st_rep_to_fun_def])


subsection ‹Executable restriction and combination laws›

text ‹
  Algebraic identities of the carrier itself: no abstract state, no
  concretization, no classifier. They say that combining and then projecting
  recovers the projected side, and that the two projections recombine by join.
  Executable trees use them to split and rebuild a routed state.
›

lemma combine_default_st_eq_restrict_sup:
  "combine_default_st A B =
     restrict_local_default_st A ⊔ restrict_global_default_st B"
  by (rule default_st_eqI) (simp split: location.split)

lemma restrict_local_default_st_combine [simp]:
  "restrict_local_default_st (combine_default_st A B) =
     restrict_local_default_st A"
  by (rule default_st_eqI) (simp split: location.split)

lemma restrict_global_default_st_combine [simp]:
  "restrict_global_default_st (combine_default_st A B) =
     restrict_global_default_st B"
  by (rule default_st_eqI) (simp split: location.split)

lemma restrict_local_default_st_split [simp]:
  "restrict_local_default_st (restrict_local_default_st A ⊔
      restrict_global_default_st B) = restrict_local_default_st A"
  by (rule default_st_eqI) (simp split: location.split)

lemma restrict_global_default_st_split [simp]:
  "restrict_global_default_st (restrict_local_default_st A ⊔
      restrict_global_default_st B) = restrict_global_default_st B"
  by (rule default_st_eqI) (simp split: location.split)

text ‹
  Combining reads only the local half of its first argument and the global half
  of its second, so a state recombines from its own two projections.
›

lemma combine_default_st_restrict_split [simp]:
  "combine_default_st (restrict_local_default_st x) (restrict_global_default_st x) = x"
  by (rule default_st_eqI) (simp split: location.split)

lemma combine_default_st_restrict_local_left [simp]:
  "combine_default_st (restrict_local_default_st x) y = combine_default_st x y"
  by (rule default_st_eqI) (simp split: location.split)

lemma combine_default_st_self_restrict_global [simp]:
  "combine_default_st x (restrict_global_default_st x) = x"
  by (rule default_st_eqI) (simp split: location.split)

subsection ‹The stores a carrier state describes›

text ‹
  A carrier state denotes the stores its function denotes. This is the one
  concretization the carrier offers to semantic statements; the represented
  function itself stays a refinement device, used to relate carrier operations
  to their counterparts on typ‹'a abs_state›. Only the classifier
  ‹𝒢› is needed besides the state, because it decides which location holds each
  name's value. A context that fixes ‹𝒢› may register
  ‹default_st_gamma 𝒢› under ‹γ _› with ‹adhoc_overloading›.
›

definition default_st_gamma ::
  "(vname => bool) => ('a::numeric_domain) default_st => store set" where
  "default_st_gamma 𝒢 s = γ (ρ⇘𝒢⇙ s)"

lemma default_st_gamma_mono:
  "s ≤ t ⟹ default_st_gamma 𝒢 s ⊆ default_st_gamma 𝒢 t"
  unfolding default_st_gamma_def by (intro gamma_state_mono default_st_to_fun_mono)

lemma default_st_gamma_bot [simp]: "default_st_gamma 𝒢 bot = {}"
  by (simp add: default_st_gamma_def)

lemma default_st_gamma_initial:
  "default_st_gamma 𝒢 (initial_default_st local_value global_value) =
   γ (λx. if 𝒢 x then global_value else local_value)"
  by (simp add: default_st_gamma_def default_st_to_fun_initial)

unbundle no default_st_syntax

end