Theory Transfer_Algebra

theory Transfer_Algebra
  imports "Voblint_CFG.CFG_Transfer" "Voblint_Domain.Nonrelational_State"
begin

section ‹The abstract-state algebra a whole-state transfer computes in›

text ‹
  A Base-style analysis carries one pointwise abstract state per program point: a
  map from variable names to abstract values. This theory fixes the operations its
  transfer functions are assembled from -- how a call resets the callee frame and
  binds its formals, how a return merges caller against callee and publishes the
  result value -- together with their soundness and monotonicity against
  ‹gamma_state›.

  It defines no interface. What an analysis supplies to the framework is a
  ‹dg_spec›: one manager-native transfer per edge action, taking the manager and
  returning a program. This theory is the algebra those transfers compute in,
  stated once so that no whole-state domain re-proves it.

  Most of the operations themselves live lower still, in the language session,
  because they are generic in the state's codomain and the concrete semantics
  runs the very same constants: const‹combine_env›, const‹enter_frame›,
  const‹bind_formals›, const‹combine_assign› and const‹enter_binding› are
  all shared. What is genuinely new here is their meaning against a
  concretization, which is the first point at which the domain session and the
  CFG session are both in scope.
›


subsection ‹The structural environment combine›

lemma combine_env_mono:
  fixes sc1 sc2 se1 se2 :: "'a::order abs_state"
  assumes "sc1 ≤ sc2" and "se1 ≤ se2"
  shows "combine_env 𝒢 sc1 se1 ≤ combine_env 𝒢 sc2 se2"
  using assms by (auto simp: combine_env_def le_fun_def)

text ‹
  Soundness of the abstract combine: combining a caller store (sound for sc) with
  a callee-exit store (sound for se) yields a store sound for ‹combine_env 𝒢 sc se›.
  A pure ‹numeric_domain› fact -- independent of any transfer function -- reused by
  both the interprocedural constraint-system soundness and the effectful pipeline.
  ‹combine_env› is the fixed structural merge the Base call boundary uses; an
  analysis that wants a different one overrides its specification's own
  environment stage instead.
›
lemma combine_env_sound [intro]:
  fixes dc de :: "'a::numeric_domain abs_state"
  assumes sc: "s ∈ γ dc" and se: "t ∈ γ de"
  shows "combine_env 𝒢 s t ∈ γ (combine_env 𝒢 dc de)"
  using assms by (auto simp: gamma_state_def le_fun_def)

subsection ‹Generic soundness and monotonicity helpers›

text ‹The two facts a call boundary keeps re-deriving: writing one variable, and binding a
  list of formals, each preserve soundness and monotonicity.  They are stated for an
  arbitrary domain and no transfer function, so every analysis picks them up rather than
  proving its own copy.›
lemma gamma_state_upd [intro]:
  fixes d :: "'a::numeric_domain abs_state"
  assumes s: "s ∈ γ d" and v: "v ∈ γ a"
  shows "s(x := v) ∈ γ (d(x := a))"
  using s v unfolding gamma_state_def by auto

text ‹
  Binding formals preserves soundness: pointwise-sound actual values bound to the
  same formals yield a sound entry state.
›
lemma bind_formals_sound [intro]:
  fixes d :: "'a::numeric_domain abs_state"
  assumes s: "s ∈ γ d"
    and vals: "list_all2 (λv a. v ∈ γ a) vs avs"
  shows "bind_formals xs vs s ∈ γ (bind_formals xs avs d)"
  using assms apply (induction xs arbitrary: vs avs s d)
  subgoal
    by simp
  subgoal for _ _ vs avs
    by (cases vs; cases avs) (auto simp: gamma_state_upd)
  done

lemma bind_formals_mono:
  fixes d1 d2 :: "'a::order abs_state"
  assumes base: "d1 ≤ d2"
    and vals: "list_all2 (≤) avs1 avs2"
  shows "bind_formals xs avs1 d1 ≤ bind_formals xs avs2 d2"
  using assms apply (induction xs arbitrary: avs1 avs2 d1 d2)
  subgoal
    by simp
  subgoal for _ _ avs1 avs2
    by (cases avs1; cases avs2) (auto simp add: le_fun_def)
  done

subsection ‹Procedure entry: binding formals and resetting the frame›

text ‹
  Procedure entry itself is not defined here. const‹enter_frame› and
  const‹enter_binding› (theory‹Voblint_VIMP.VIMP_Proc›) are already generic in
  the state's codomain, and the concrete semantics is const‹enter_binding› at
  the reset value ‹0› and the evaluator const‹aval›
  (const‹call_enter›). An abstract domain runs the same constant at its own
  ‹top› and its own evaluator, so the two can never drift apart: there is one
  definition and two instances of it.

  What this theory adds is the pair of facts that relate those two instances --
  the concrete entered store lands in the concretization of the abstract one, and
  the abstract one is monotone. Neither is expressible below here, because
  const‹gamma_state› lives in the domain session and const‹enter_binding› in
  the language session.
›

lemma enter_frame_sound [intro]:
  fixes reset_val :: "'a::numeric_domain"
  assumes sv: "s ∈ γ d" and reset_full: "γ reset_val = UNIV"
  shows "enter_state 𝒢 s ∈ γ (enter_frame 𝒢 reset_val d)"
  unfolding gamma_state_def enter_state_def
  using gamma_stateD[OF sv] reset_full by auto

lemma enter_frame_mono:
  fixes reset_val :: "'a::order"
  assumes "d1 ≤ d2"
  shows "enter_frame 𝒢 reset_val d1 ≤ enter_frame 𝒢 reset_val d2"
  by (simp add: assms le_funD le_funI)

text ‹
  const‹bind_formals›'s own soundness and monotonicity are relational in the
  actual values, but that is not the shape a domain owns: an evaluator's
  soundness and monotonicity are pointwise in one actual. This turns the one into
  the other, so the entry lemmas below can take the fact a caller already has and
  nobody builds a const‹list_all2› by hand.
›

lemma list_all2_map_mapI [intro]:
  assumes "⋀x. x ∈ set xs ⟹ R (f x) (g x)"
  shows "list_all2 R (map f xs) (map g xs)"
  using assms by (induction xs) auto

lemma enter_binding_sound_list_all2 [intro]:
  fixes reset_val :: "'a::numeric_domain"
  assumes sv: "s ∈ γ d" and reset_full: "γ reset_val = UNIV"
    and vals: "list_all2 (λv a. v ∈ γ a)
                 (map (λe. ⟦e⟧e s) es) (map (λe. aval_abs e d) es)"
  shows "enter_binding 𝒢 0 aval xs es s
           ∈ γ (enter_binding 𝒢 reset_val aval_abs xs es d)"
  unfolding enter_binding_def enter_state_def[symmetric]
  by (rule bind_formals_sound[OF enter_frame_sound[OF sv reset_full] vals])

lemma enter_binding_sound [intro]:
  fixes reset_val :: "'a::numeric_domain"
  assumes sv: "s ∈ γ d" and reset_full: "γ reset_val = UNIV"
    and eval: "⋀e. e ∈ set es ⟹ ⟦e⟧e s ∈ γ (aval_abs e d)"
  shows "enter_binding 𝒢 0 aval xs es s
           ∈ γ (enter_binding 𝒢 reset_val aval_abs xs es d)"
proof (rule enter_binding_sound_list_all2[OF sv reset_full])
  show "list_all2 (λv a. v ∈ γ a)
          (map (λe. ⟦e⟧e s) es) (map (λe. aval_abs e d) es)"
    by (rule list_all2_map_mapI) (rule eval)
qed

lemma enter_binding_mono:
  fixes reset_val :: "'a::order"
  assumes state_le: "d1 ≤ d2"
    and eval_mono: "⋀e. e ∈ set es ⟹ aval_abs e d1 ≤ aval_abs e d2"
  shows "enter_binding 𝒢 reset_val aval_abs xs es d1
           ≤ enter_binding 𝒢 reset_val aval_abs xs es d2"
  unfolding enter_binding_def
proof (rule bind_formals_mono[OF enter_frame_mono[OF state_le]])
  show "list_all2 (≤) (map (λe. aval_abs e d1) es) (map (λe. aval_abs e d2) es)"
    by (rule list_all2_map_mapI) (rule eval_mono)
qed

text ‹
  A call site reads the formals and actuals off typ‹call_info› rather than
  passing them separately, but that projection stays at the call site: a domain
  writes ‹enter_binding 𝒢 top ev (ci_formals ci) (ci_args ci)›, exactly as the
  return side writes ‹combine♯ 𝒢 (ci_dst ci)›. No constant here wraps it.

  The caller's own continuation is not computed here either. Goblint's
  ‹Spec.enter› answers caller/callee pairs and ‹constraints.ml› hands the caller
  half on to ‹combine_env›; a specification's entry field answers the same
  pairs, so a domain that wants to drop caller facts a callee could invalidate
  --- Goblint's ‹varEq› filters its caller state by taint --- returns the
  filtered state as the continuation half of its own alternatives. Nothing in
  this algebra chooses that half: the operations below are what a whole-state
  domain computes each half ∗‹with›.
›

subsection ‹The structural return combine›

text ‹The return-value write is a single-slot update, hence monotone in both the
  written value and the state it writes into.  Any combine built over it inherits
  monotonicity from this one fact.›

lemma combine_assign_mono:
  fixes s1 s2 :: "'a::order abs_state"
  assumes v: "v1 ≤ v2" and s: "s1 ≤ s2"
  shows "combine_assign dst v1 s1 ≤ combine_assign dst v2 s2"
  using assms by (cases dst) (auto simp: le_fun_def)

text ‹
  Return combination joins caller locals with callee globals and then assigns the
  callee's @{const ret_var} to the optional destination.  The ordinary abstract
  state update publishes the result without domain-specific return machinery.
›
definition combine_collect_abs ::
    "(vname ⇒ bool) ⇒ vname option ⇒ 'a abs_state ⇒ 'a abs_state ⇒ 'a abs_state"
    ("combine♯") where
  "combine_collect_abs 𝒢 dst dc de =
     combine_assign dst (de ret_var) (combine_env 𝒢 dc de)"

lemma combine_collect_abs_mono:
  fixes dc1 dc2 de1 de2 :: "'a::order abs_state"
  assumes c: "dc1 ≤ dc2" and e: "de1 ≤ de2"
  shows "combine♯ 𝒢 dst dc1 de1 ≤ combine♯ 𝒢 dst dc2 de2"
  unfolding combine_collect_abs_def
  by (rule combine_assign_mono[OF le_funD[OF e] combine_env_mono[OF c e]])

text ‹
  The binary env-combine is the destination-free instance of the
  return-threaded combine: with no destination the return slot is not written.
  Together with the writing case these reduce the abstract combine under a
  ‹cases dst›, so no caller unfolds its definition by hand.
›
lemma combine_collect_abs_None[simp]:
  "combine♯ 𝒢 None a b = combine_env 𝒢 a b"
  by (simp add: combine_collect_abs_def)

lemma combine_collect_abs_Some[simp]:
  "combine♯ 𝒢 (Some x) a b = (combine_env 𝒢 a b)(x := b ret_var)"
  by (simp add: combine_collect_abs_def)

subsection ‹Soundness of the structural return combine›


text ‹
  Soundness of the abstract combine including result publication.  A pure
  @{class numeric_domain} fact: the destination slot is sound because the callee's
  @{const ret_var} slot is, and every other slot is handled by
  @{thm combine_env_sound}.
›
lemma combine_collect_sound [intro]:
  fixes dc de :: "'a::numeric_domain abs_state"
  assumes sc: "s ∈ γ dc" and se: "t ∈ γ de"
  shows "combine_collect 𝒢 dst s t ∈ γ (combine♯ 𝒢 dst dc de)"
  unfolding combine_collect_def combine_collect_abs_def
  using combine_env_sound[OF sc se] gamma_stateD[OF se]
  by (cases dst) (auto simp add: gamma_state_upd)

subsection ‹The C-faithful initial store set›

text ‹
  const‹cinit_stores› (theory‹Voblint_VIMP.VIMP_Globals›) is the C-faithful initial
  store set. Any analysis that uses a domain-specific abstract seed ‹s0›
  satisfying ‹cinit_stores 𝒢 ⊆ γ s0› may state its soundness
  theorem against ‹cinit_stores 𝒢› rather than ‹UNIV›, matching VIMP's
  C-like initialization semantics.›


end