Theory DG_Result_Construction

theory DG_Result_Construction
  imports
    "Voblint_Framework.DG_Analysis_Adapter"
    "Voblint_VIMP.VIMP_Program"
    "Voblint_Exec.Exec_DG_State"
begin

section ‹What a solved D/G system publishes›

text ‹
  Every analysis, at every context policy and every solver discipline, turns the
  solver's answer -- a covered key set and a map from unknowns to ‹dg_state›s over
  its carrier -- into an type‹solved_table› table of the locals. This
  theory states that construction once, over an arbitrary solved pair, so an
  analysis's result table is one application rather than a rewritten body.

  Publishing a local unknown means two steps in sequence.
  const‹canonicalize_lift› collapses a stored const‹Lifted› payload that
  describes no store to const‹Bot›, so a dead point reads as dead; the publication
  map ‹rd› then turns the carrier into the value the table publishes. Coverage is
  separate from deadness: a key the solver never visited is absent from the
  table, and const‹lookup_table› answers const‹Bot› for it without any
  claim about the program.
›

text ‹
  ‹sol› is the already-solved pair, not the solve function. Passing the pair keeps
  one solve per table in generated code -- the argument is evaluated once and the
  per-point closure captures it -- which is why no analysis needs a separate
  ‹[code]› equation with an explicit ‹let›.

  What a point publishes is read off its local unknown together with the solved
  global environment, each name read at its key ‹Inr (gk n)›: ‹rc› recombines
  the local half with that environment into the state the point describes. An analysis that keeps every variable in the local half passes a
  recombination that ignores the global.
›

definition dg_result_for ::
    "('s ⇒ 'v) ⇒ ('s ⇒ bool) ⇒ ('s lifted ⇒ ('n ⇒ 'g) ⇒ 's lifted) ⇒ ('n ⇒ 'k)
     ⇒ (pp × 'c) set × (pp × 'c + 'k ⇒ ('s lifted, 'g) dg_state)
     ⇒ ('c, 'v) solved_table" where
  "dg_result_for rd emp rc gk sol =
     Solved_Table (fst sol)
       (λv ctx. map_lift rd (canonicalize_lift emp
          (rc (dg_local (snd sol (Inl (v, ctx)))) (genv gk (snd sol)))))"

lemma covered_keys_dg_result_for [simp]:
  "covered_keys (dg_result_for rd emp rc gk sol) = fst sol"
  unfolding dg_result_for_def by simp

text ‹
  The one fact every soundness bridge needs about the table: a covered key reads
  back the normalized recombined state, an uncovered one answers const‹Bot›.
›

lemma lookup_table_dg_result_for [simp]:
  "lookup_table (dg_result_for rd emp rc gk sol) v ctx
     = (if (v, ctx) ∈ fst sol
        then map_lift rd (canonicalize_lift emp
               (rc (dg_local (snd sol (Inl (v, ctx)))) (genv gk (snd sol))))
        else Bot)"
  unfolding dg_result_for_def lookup_table_def by simp

text ‹
  Normalizing before publication agrees with normalizing after it whenever the
  two emptiness tests agree, so a soundness bridge stated after publication
  applies to the table built before it.
›

lemma map_lift_canonicalize_lift:
  assumes "⋀s. emp s = emptyV (rd s)"
  shows "map_lift rd (canonicalize_lift emp d) = canonicalize_lift emptyV (map_lift rd d)"
  by (cases d) (simp_all add: assms normalize_lift_def)

text ‹Collapsing a payload that denotes nothing to const‹Bot› does not change
  what the value denotes.›

lemma gamma_lift_canonicalize_lift:
  assumes "⋀v. emptyV v ⟹ gm v = {}"
  shows "gamma_lift gm (canonicalize_lift emptyV x) = gamma_lift gm x"
  by (cases x) (simp_all add: normalize_lift_def assms)

end