Theory DG_Ownership_Split_Spec

theory DG_Ownership_Split_Spec
  imports DG_Local_State_Spec State_Restriction "Voblint_Solver.Strategy_Program_Fold"
begin

section ‹Lifting a whole-state analysis onto the global channel›

text ‹
  Every other specification in this session is local-only: it answers from the
  local value and never touches ‹man_global› or ‹man_sideg›. This
  theory takes such a specification and returns one that does the opposite, and is
  the reason those capabilities exist.

  The lifted specification uses one carrier for ‹D› and ‹G›, splitting each
  state by variable ownership: const‹combine_env›'s predicate is the source
  language's own declaration of which names are global, so the global names live on
  the shared channel and the rest on the program point's own unknown. The split is
  not a representation choice: a return recombines two concrete stores by that same
  ownership rule, keeping the caller's locals and taking the globals from the
  callee.

  Each lifted transfer therefore reads the shared fact, rejoins it with the local
  half to recover the whole state, runs the argument's transfer on a manager
  holding that whole state, and splits the result back -- publishing the global
  half with ‹man_sideg› and answering with the local half. Its compiled tree
  reads the source, queries the global key, publishes a ‹Side›, and answers,
  which is what makes it the only specification here whose equations carry a
  genuine ‹QueryG› and ‹Side›.

  Because it consumes and produces a specification, it is a lifter in Goblint's
  sense -- a ‹Spec2Spec› functor, like the privatizations that give a Base
  analysis its shared-state discipline -- not a second way of writing an analysis.
  The argument keeps its own transfers; only what a transfer's local value means,
  and what happens to its result, change.

  The construction is generic in the carrier -- the same three operations exist on
  function-valued states and on the solver's association lists -- but not in the
  ownership rule, which is always const‹combine_env› and the two
  restrictions to the global and the local half at that carrier.
›


subsection ‹The ownership-splitting wrapper›

text ‹
  The pattern is stated once, over any carrier that can merge a local and a global
  half and project each back out. Which carrier is a separate question from what
  the wrapper does: the same three operations exist on function-valued states and
  on the solver's association lists, so the executable mirror is this definition at
  other arguments rather than a second development.

  It wraps a transfer rather than a pure function. What the wrapped transfer sees
  is a manager whose local value is the merged whole state; its own capabilities
  are the outer ones, which a whole-state transfer never calls. That is what makes
  the wrapper a specification-to-specification lifter -- Goblint's
  ‹Spec2Spec› -- instead of a second way of writing a transfer.
›

definition ownership_split_transfer_gen ::
  "('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ ('d ⇒ 'd)
   ⇒ ('x,'k,unit,'d::bounded_semilattice_sup_bot,'d) man_transfer
   ⇒ ('x,'k,unit,'d,'d) man_transfer"
where
  "ownership_split_transfer_gen cmb rg rl T m =
     do {
       g ← man_global m ();
       res ← T (m⦇man_local := cmb (man_local m) g⦈);
       _ ← man_sideg m () (rg res);
       sp_return (rl res)
     }"

text ‹
  A return combine is the same pattern with two values to merge: the caller
  continuation becomes the wrapped transfer's local value, and the callee exit is
  merged against the same shared fact before it is handed over. Merging both sides
  means the wrapped transfer sees two whole states and needs to know nothing about
  the split -- in particular the callee's globals come from the shared channel,
  not from its own locally-restricted copy.
›

definition ownership_split_combine_transfer_gen ::
  "('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ ('d ⇒ 'd)
   ⇒ ('x,'k,unit,'d::bounded_semilattice_sup_bot,'d) man_combine_transfer
   ⇒ ('x,'k,unit,'d,'d) man_combine_transfer"
where
  "ownership_split_combine_transfer_gen cmb rg rl T m de =
     do {
       g ← man_global m ();
       res ← T (m⦇man_local := cmb (man_local m) g⦈) (cmb de g);
       _ ← man_sideg m () (rg res);
       sp_return (rl res)
     }"

text ‹
  Entry is the one boundary the pattern cannot be reused at verbatim: a call
  answers a list of alternatives rather than one successor value. The shared
  fact is read once and the wrapped entry runs once against the merged caller
  state, so every alternative comes out of the same whole state --- an actual
  may mention a global, so the alternatives cannot be computed from the local
  half alone. Each alternative then has both of its halves split, and the answer
  is the list of split pairs.

  What reaches the shared channel is the join, over every alternative, of both
  halves' global projections. A continuation is as much a product of the merged
  state as its callee entry is, and the split discards its global half;
  publishing only one of the two would need a proof that the other carries no
  global information the environment does not already hold.
›

definition ownership_split_enter_sides ::
  "('d ⇒ 'd) ⇒ ('d::bounded_semilattice_sup_bot) enter_result list ⇒ 'd"
where
  "ownership_split_enter_sides rg pairs =
     (⨆(cont, entry)←pairs. rg cont ⊔ rg entry)"

lemma ownership_split_enter_sides_Nil [simp]:
  "ownership_split_enter_sides rg [] = bot"
  by (simp add: ownership_split_enter_sides_def)

lemma ownership_split_enter_sides_Cons [simp]:
  "ownership_split_enter_sides rg ((cont, entry) # pairs)
     = rg cont ⊔ rg entry ⊔ ownership_split_enter_sides rg pairs"
  by (simp add: ownership_split_enter_sides_def)

text ‹
  What the joint publication bounds, and what it is bounded by. The two member
  rules are what a soundness proof reaches for once it has selected a covering
  alternative: that pair's two halves are each below what the call published, so
  a concretization taken against the published global still contains them. They
  stay untagged --- their conclusions are bare inequalities in ‹rg› and would
  broaden proof search wherever a ‹≤› goal appears.
›

lemma ownership_split_enter_sides_le_iff:
  "ownership_split_enter_sides rg pairs ≤ g
     ⟷ (∀(cont, entry) ∈ set pairs. rg cont ≤ g ∧ rg entry ≤ g)"
  by (induction pairs) auto

definition ownership_split_enter_transfer_gen ::
  "('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ ('d ⇒ 'd)
   ⇒ ('x,'k,unit,'d::bounded_semilattice_sup_bot,'d) man_enter_transfer
   ⇒ ('x,'k,unit,'d,'d) man_enter_transfer"
where
  "ownership_split_enter_transfer_gen cmb rg rl T m =
     do {
       g ← man_global m ();
       pairs ← T (m⦇man_local := cmb (man_local m) g⦈);
       _ ← man_sideg m () (ownership_split_enter_sides rg pairs);
       sp_return (map (λ(cont, entry). (rl cont, rl entry)) pairs)
     }"

subsection ‹What the compiled tree reads and publishes›

text ‹
  Soundness judges a transfer by these two observations, so they are proved once
  here, on the pattern at a whole-state transfer, rather than per carrier: the
  answer is the local projection of the merged whole state, and the contribution at
  the routed key is its global projection.
›

lemma dg_local_traverse_ownership_split_transfer_gen [simp]:
  "dg_local (traverse_program
             (transfer_program (ownership_split_transfer_gen cmb rg rl (local_transfer f))
                src (λ_. gk)) τ)
     = rl (f (cmb (dg_local (τ src)) (dg_global (τ (Inr gk)))))"
  by (simp add: traverse_transfer_program ownership_split_transfer_gen_def
      local_transfer_def sp_compile_with_def sp_bind_def sp_return_def)

lemma dg_global_sides_ownership_split_transfer_gen [simp]:
  "dg_global (sides_of_program
            (transfer_program (ownership_split_transfer_gen cmb rg rl (local_transfer f))
               src (λ_. gk)) τ (Inr gk))
     = rg (f (cmb (dg_local (τ src)) (dg_global (τ (Inr gk)))))"
  by (simp add: sides_transfer_program ownership_split_transfer_gen_def
      local_transfer_def sp_compile_with_def sp_bind_def sp_return_def)

lemma dg_local_traverse_ownership_split_combine_transfer_gen [simp]:
  "dg_local (traverse_program
             (combine_transfer_program
                (ownership_split_combine_transfer_gen cmb rg rl (local_combine_transfer h))
                src_cc src_ex (λ_. gk)) τ)
     = rl (h (cmb (dg_local (τ src_cc)) (dg_global (τ (Inr gk))))
             (cmb (dg_local (τ src_ex)) (dg_global (τ (Inr gk)))))"
  by (simp add: traverse_combine_transfer_program ownership_split_combine_transfer_gen_def
      local_combine_transfer_def sp_compile_with_def sp_bind_def sp_return_def)

lemma dg_global_sides_ownership_split_combine_transfer_gen [simp]:
  "dg_global (sides_of_program
            (combine_transfer_program
               (ownership_split_combine_transfer_gen cmb rg rl (local_combine_transfer h))
               src_cc src_ex (λ_. gk)) τ (Inr gk))
     = rg (h (cmb (dg_local (τ src_cc)) (dg_global (τ (Inr gk))))
             (cmb (dg_local (τ src_ex)) (dg_global (τ (Inr gk)))))"
  by (simp add: sides_combine_transfer_program ownership_split_combine_transfer_gen_def
      local_combine_transfer_def sp_compile_with_def sp_bind_def sp_return_def)

text ‹A whole-state transfer never asks, so the query channel the generator
  installs around the wrapper leaves it unchanged.›

lemma ownership_split_transfer_gen_local_outer_man [simp]:
  "ownership_split_transfer_gen cmb rg rl (local_transfer f) (outer_man Q m)
     = ownership_split_transfer_gen cmb rg rl (local_transfer f) m"
  by (simp add: ownership_split_transfer_gen_def local_transfer_def)
text ‹
  What the lifter reads, in the same terms. The shared slot is read before the
  wrapped transfer runs, so it is a dependency of the call whatever the wrapped
  transfer does --- and it stays one even when nothing is published there,
  which is exactly why dependencies are tracked apart from
  const‹enter_runs›'s publications. The trailing ‹man_sideg› writes rather
  than reads, so it contributes no dependency of its own.
›

lemma enter_deps_ownership_split_enter_transfer_gen [intro]:
  fixes sigma :: "'x + 'k ⇒ ('d::bounded_semilattice_sup_bot,'d) dg_state"
  assumes T: "enter_deps T (mk_dg_man (cmb d (dg_global (sigma (Inr (unknown_of ()))))) unknown_of)
                sigma pairs deps"
  shows "enter_deps (ownership_split_enter_transfer_gen cmb rg rl T) (mk_dg_man d unknown_of) sigma
           (map (λ(cont, entry). (rl cont, rl entry)) pairs)
           ({Inr (unknown_of ())} ∪ deps)"
  unfolding enter_deps_def
proof (intro allI)
  fix K
  show "dep_aux sigma (ownership_split_enter_transfer_gen cmb rg rl T (mk_dg_man d unknown_of) K)
          = ({Inr (unknown_of ())} ∪ deps)
            ∪ dep_aux sigma (K (map (λ(cont, entry). (rl cont, rl entry)) pairs))"
    by (simp add: ownership_split_enter_transfer_gen_def sp_bind_def sp_return_def
        enter_depsD[OF T] Un_assoc)
qed
text ‹
  How the lifter behaves under a solution, in the same terms as the transfer it
  wraps. The manager has to be a concrete const‹mk_dg_man›: for an arbitrary
  one ‹man_sideg m ()› is opaque and the slot it publishes at cannot be named,
  so there would be nothing to state.

  The wrapped transfer is run at the reconstructed caller state, so its
  alternatives are the ones computed against the whole state rather than the
  local half --- that is the step a caller-local-only entry could not perform.
  Its own publications survive in ‹pub›, and the lifter adds exactly one
  contribution of its own, at the routed slot.
›

lemma enter_runs_ownership_split_enter_transfer_gen [intro]:
  fixes sigma :: "'x + 'k ⇒ ('d::bounded_semilattice_sup_bot,'d) dg_state"
  assumes T: "enter_runs T (mk_dg_man (cmb d (dg_global (sigma (Inr (unknown_of ()))))) unknown_of)
                sigma pairs pub"
  shows "enter_runs (ownership_split_enter_transfer_gen cmb rg rl T) (mk_dg_man d unknown_of) sigma
           (map (λ(cont, entry). (rl cont, rl entry)) pairs)
           (pub ⊔ (bot(Inr (unknown_of ()) := DG bot (ownership_split_enter_sides rg pairs))))"
  unfolding enter_runs_def
proof (intro allI conjI)
  fix K
  show "traverse_rhs (ownership_split_enter_transfer_gen cmb rg rl T (mk_dg_man d unknown_of) K) sigma
          = traverse_rhs (K (map (λ(cont, entry). (rl cont, rl entry)) pairs)) sigma"
    by (simp add: ownership_split_enter_transfer_gen_def sp_bind_def sp_return_def
        enter_runsD_traverse[OF T])
next
  fix K
  show "sides_of_rhs (ownership_split_enter_transfer_gen cmb rg rl T (mk_dg_man d unknown_of) K) sigma
          = (pub ⊔ (bot(Inr (unknown_of ()) := DG bot (ownership_split_enter_sides rg pairs))))
            ⊔ sides_of_rhs (K (map (λ(cont, entry). (rl cont, rl entry)) pairs)) sigma"
    by (simp add: ownership_split_enter_transfer_gen_def sp_bind_def sp_return_def
        enter_runsD_sides[OF T] sup_fun_def fun_upd_def)
       (auto simp: fun_eq_iff ac_simps split: if_splits)
qed

subsection ‹The specification-to-specification lifter›

text ‹
  Every edge transfer of the argument specification is wrapped, entry through its
  own list-shaped wrapper, and the whole return pipeline is wrapped once rather
  than stage by stage: wrapping each stage separately would split and re-merge
  between them, and the second merge would read the shared fact again instead of
  the first stage's own result. So const‹dgs_combine_env› stays at its identity
  default and const‹dgs_combine_assign› carries the wrapped
  const‹dg_spec_combine_transfer› of the argument.
›

definition ownership_split_lift_gen ::
  "('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ ('d ⇒ 'd)
   ⇒ ('x,'k,unit,'d::bounded_semilattice_sup_bot,'d) dg_spec
   ⇒ ('x,'k,unit,'d,'d) dg_spec"
where
  "ownership_split_lift_gen cmb rg rl S = local_dg_spec_template⦇
     dgs_skip := ownership_split_transfer_gen cmb rg rl (skip♯ S),
     dgs_assign := (λx e. ownership_split_transfer_gen cmb rg rl (assign♯ S x e)),
     dgs_special := (λsc x. ownership_split_transfer_gen cmb rg rl (special♯ S sc x)),
     dgs_branch := (λb pol. ownership_split_transfer_gen cmb rg rl (branch♯ S b pol)),
     dgs_body := (λp. ownership_split_transfer_gen cmb rg rl (body♯ S p)),
     dgs_return := (λe p. ownership_split_transfer_gen cmb rg rl (return♯ S e p)),
     dgs_enter := (λci. ownership_split_enter_transfer_gen cmb rg rl (enter♯ S ci)),
     dgs_event := (λevt. ownership_split_transfer_gen cmb rg rl (event♯ S evt)),
     dgs_combine_assign :=
       (λci. ownership_split_combine_transfer_gen cmb rg rl (dg_spec_combine_transfer S ci)) ⦈"

text ‹Both eliminate a constructed wrapper specification and expose the
  corresponding transfer wrapper, in a terminating direction, so they fire
  wherever a lifted specification meets the edge or combine dispatch.›

lemma dg_spec_step_ownership_split_lift_gen [simp]:
  "dg_spec_step (ownership_split_lift_gen cmb rg rl S) a
     = ownership_split_transfer_gen cmb rg rl (dg_spec_step S a)"
  unfolding ownership_split_lift_gen_def by (cases a) simp_all

lemma dgs_enter_ownership_split_lift_gen [simp]:
  "enter♯ (ownership_split_lift_gen cmb rg rl S) ci
     = ownership_split_enter_transfer_gen cmb rg rl (enter♯ S ci)"
  unfolding ownership_split_lift_gen_def by simp

lemma dgs_query_ownership_split_lift_gen [simp]:
  "dgs_query (ownership_split_lift_gen cmb rg rl S) m q = sp_return ⊤"
  unfolding ownership_split_lift_gen_def by simp

lemma dg_spec_combine_transfer_ownership_split_lift_gen [simp]:
  "dg_spec_combine_transfer (ownership_split_lift_gen cmb rg rl S) ci
     = ownership_split_combine_transfer_gen cmb rg rl (dg_spec_combine_transfer S ci)"
  unfolding dg_spec_combine_transfer_def ownership_split_lift_gen_def
  by (simp add: local_transfer_def local_combine_transfer_def)

text ‹The lifter preserves well-formedness: it reads the shared slot, runs the
  wrapped transfer at a manager built the same way, publishes once and answers.
  Every step of that is a closure lemma.›

lemma sp_wf_dgs_combine_assign_ownership_split_lift_gen [intro]:
  assumes "dg_spec_wf S"
  shows
    "sp_wf (combine_assign♯ (ownership_split_lift_gen cmb rg rl S) ci (mk_dg_man d unknown_of) ex)"
  unfolding ownership_split_lift_gen_def
  by (auto simp: ownership_split_combine_transfer_gen_def
      intro!: sp_wf_bind dg_spec_wf_combine[OF assms])

lemma dg_spec_wf_ownership_split_lift_gen [intro]:
  assumes "dg_spec_wf S"
  shows "dg_spec_wf (ownership_split_lift_gen cmb rg rl S)"
  unfolding dg_spec_wf_def
  by (auto simp: ownership_split_transfer_gen_def ownership_split_enter_transfer_gen_def
      ownership_split_combine_transfer_gen_def
      intro!: sp_wf_bind dg_spec_wf_step_ask[OF assms] dg_spec_wf_enter[OF assms]
        dg_spec_wf_combine[OF assms])

end