Theory DG_Live_Unknowns

theory DG_Live_Unknowns
  imports DG_Analysis "Voblint_Compile.Live_Nodes"
begin

section ‹Which solved keys a terminating solve is closed on›

text ‹
  The key set a terminating solve returns is not one the soundness argument can use as it
  stands: it may hold keys the solver queried under an earlier value and no longer reads,
  and keys of procedures whose result it never solved.  ‹live_unknowns› keeps a solved key
  when its node belongs to a procedure (theory‹Voblint_Compile.Live_Nodes›) whose result
  is solved at the same context.  From what each generated equation reads under the final
  valuation, ‹live_unknowns_cover› shows that set closed under every intra edge, every call
  continuation and every call that enters a live state, with no premise beyond termination
  and a well-formed program.  The endpoints below restate the routed soundness over these
  keys and carry it to the published table.
›

subsection ‹What a routed equation reads›

lemma dep_aux_routed_node_rhs:
  assumes wf: "⋀w. ∀q ∈ set (routed_contribution_programs pred_sel site_sel route
                     it cmb extra g cx w). sp_wf q"
  shows "dep_aux τ (routed_node_rhs pred_sel site_sel buffer_key_at route it cmb extra g bot0 d0 g0
           (v, cx))
     = (⋃q∈set (routed_contribution_programs pred_sel site_sel route it cmb extra g cx v).
          dep_program τ q)"
  by (cases "v = cfg_entry g")
     (simp_all add: routed_node_rhs_def Let_def dep_program_side_rhs_fold_dg_char[OF wf])

lemma dep_L_routed_node_rhs_intra:
  assumes fin: "finite (intra g)" and e: "(u, a, v) ∈ intra g"
    and wf: "⋀w. ∀q ∈ set (routed_contribution_programs
                     intra_predecessor_addr_list call_site_list route
                     (λcx src a. dg_spec_edge_program S a src slot) cmb extra g cx w). sp_wf q"
  shows "(u, cx) ∈ depL (routed_node_rhs intra_predecessor_addr_list call_site_list buffer_key_at route
            (λcx src a. dg_spec_edge_program S a src slot) cmb extra g bot0 d0 g0) τ (v, cx)"
proof -
  have "dg_spec_edge_program S a (Inl (u, cx)) slot
          ∈ set (routed_contribution_programs intra_predecessor_addr_list call_site_list route
                   (λcx src a. dg_spec_edge_program S a src slot) cmb extra g cx v)"
    using e fin
    by (force simp: routed_contribution_programs_def intra_predecessor_addr_list_def
        intra_predecessors_def)
  then show ?thesis
    unfolding depL_def dep_def
    using dep_dg_spec_edge_program_source[of "Inl (u, cx)" τ S a slot]
    by (auto simp: dep_aux_routed_node_rhs[OF wf])
qed

lemma dep_program_routed_call_program:
  assumes wfS: "dg_spec_wf S"
  shows "dep_program τ (routed_call_program S global_of seed resolve is_bot route ctx ca cc v)
     = insert (Inl (cc, ctx))
         (⋃q∈set (resolve v cc ca (dg_local (τ (Inl (cc, ctx))))).
            dep_program τ (routed_callee_call_program S global_of seed route is_bot ctx ca cc
                        (dg_local (τ (Inl (cc, ctx)))) q))"
  by (simp add: routed_call_program_def sp_compile_bind sp_wf_observes
      dep_program_side_rhs_fold_dg_char[OF sp_wf_routed_callee_call_programs[OF wfS]])

lemma routed_call_program_mem:
  assumes fin: "finite (calls g)" and e: "(u, ca, FunctionEntry q, k) ∈ calls g"
  shows "routed_call_program S global_of seed resolve is_bot route cx ca u k
           ∈ set (routed_contribution_programs pred_sel call_site_list route it
                    (routed_call_program S global_of seed resolve is_bot) extra g cx k)"
proof -
  have "(u, ca) ∈ set (call_site_list g k)" using e fin by auto
  then show ?thesis by (rule routed_contribution_programs_combineI)
qed

lemma dep_L_routed_node_rhs_call_site:
  assumes fin: "finite (calls g)" and e: "(u, ca, FunctionEntry q, k) ∈ calls g"
    and wfS: "dg_spec_wf S"
    and wf: "⋀w. ∀q ∈ set (routed_contribution_programs pred_sel call_site_list route it
                     (routed_call_program S global_of seed resolve is_bot) extra g cx w). sp_wf q"
  shows "(u, cx) ∈ depL (routed_node_rhs pred_sel call_site_list buffer_key_at route it
            (routed_call_program S global_of seed resolve is_bot) extra g bot0 d0 g0) τ (k, cx)"
proof -
  have "Inl (u, cx)
          ∈ dep_program τ (routed_call_program S global_of seed resolve is_bot route cx ca u k)"
    by (simp add: dep_program_routed_call_program[OF wfS])
  then show ?thesis
    unfolding depL_def dep_def
    using routed_call_program_mem
            [OF fin e, of S global_of seed resolve is_bot route cx pred_sel it extra]
    by (auto simp: dep_aux_routed_node_rhs[OF wf])
qed

lemma dep_L_routed_node_rhs_callee_result:
  assumes fin: "finite (calls g)" and e: "(u, ca, FunctionEntry q, k) ∈ calls g"
    and deps: "enter_deps (enter♯ S (call_info_of ca q))
                 (mk_dg_man (dg_local (τ (Inl (u, cx)))) global_of) τ pairs D"
    and mem: "(c, ent) ∈ set pairs"
    and res: "q ∈ set (resolve k u ca (dg_local (τ (Inl (u, cx)))))"
    and nb: "¬ is_bot ent"
    and wfS: "dg_spec_wf S"
    and wf: "⋀w. ∀q ∈ set (routed_contribution_programs pred_sel call_site_list route it
                     (routed_call_program S global_of seed resolve is_bot) extra g cx w). sp_wf q"
  shows "(FunctionResult q, route u cx ent ca)
           ∈ depL (routed_node_rhs pred_sel call_site_list buffer_key_at route it
                (routed_call_program S global_of seed resolve is_bot) extra g bot0 d0 g0) τ (k, cx)"
proof -
  let ?d = "dg_local (τ (Inl (u, cx)))"
  have callee: "dep_program τ (routed_callee_call_program S global_of seed route is_bot cx ca u ?d q)
      = D ∪ dep_program τ (side_rhs_fold_dg bot
          (map (routed_call_alternative_program S global_of seed route is_bot cx ca u q) pairs))"
    unfolding routed_callee_call_program_def sp_compile_bind
    by (rule enter_depsD[OF deps, simplified])
  have "Inl (FunctionResult q, route u cx ent ca)
          ∈ dep_program τ (routed_callee_call_program S global_of seed route is_bot cx ca u ?d q)"
  proof -
    have "Inl (FunctionResult q, route u cx ent ca)
            ∈ dep_program τ (routed_call_alternative_program S global_of seed route is_bot cx ca u q
                           (c, ent))"
      using nb by (simp add: routed_call_alternative_program_def Let_def)
    then show ?thesis
      unfolding callee
        dep_program_side_rhs_fold_dg_char[OF sp_wf_routed_call_alternative_programs[OF wfS]]
      using mem by auto
  qed
  then have "Inl (FunctionResult q, route u cx ent ca)
               ∈ dep_program τ (routed_call_program S global_of seed resolve is_bot route cx ca u k)"
    using res by (auto simp: dep_program_routed_call_program[OF wfS])
  then show ?thesis
    unfolding depL_def dep_def
    using routed_call_program_mem
            [OF fin e, of S global_of seed resolve is_bot route cx pred_sel it extra]
    by (auto simp: dep_aux_routed_node_rhs[OF wf])
qed

subsection ‹Keys of a solve whose activation returned›

text ‹
  The solved key set is closed under the equations' reads (‹sol_vars_dep_closed›).
  Walking those reads backwards, ‹live_unknowns_cover› shows ‹live_unknowns› closed
  under intra edges, call continuations and the entries of callees a call actually enters.
›

context dg_analysis
begin

lemma analysis_contribs_wf [simp]:
  "∀q ∈ set (routed_contribution_programs intra_predecessor_addr_list call_site_list
       (route (declared_global p))
       (λctx' src a. dg_spec_edge_program (analysis_spec (declared_global p) p) a src
          global_of)
       (routed_call_program (analysis_spec (declared_global p) p) global_of seed rsv
          (λd. d = Bot))
       (routed_entry_seed_programs seed) cfg cx w). sp_wf q"
  by (rule routed_contribution_programs_wf) auto

lemma sol_vars_dep_closed:
  assumes solves: "terminates (declared_global p) p"
    and x: "x ∈ sol_vars (declared_global p) p"
    and y: "y ∈ depL
       (routed_node_rhs intra_predecessor_addr_list call_site_list (λ_. buffer_key)
          (route (declared_global p))
          (λctx' src a. dg_spec_edge_program (analysis_spec (declared_global p) p) a src global_of)
          (routed_call_program (analysis_spec (declared_global p) p) global_of seed
             (static_resolve (prog_cfg p)) (λd. d = Bot))
          (routed_entry_seed_programs seed)
          (prog_cfg p) Bot (Lifted init_st) (place_rg (declared_global p) (Lifted init_st)))
       (sol_env (declared_global p) p) x"
  shows "y ∈ sol_vars (declared_global p) p"
  using pp_routed[OF solves] x y by blast

lemma prog_cfg_finite: "finite (intra (prog_cfg p))" "finite (calls (prog_cfg p))"
  unfolding prog_cfg_def by (simp_all add: compile_prog_finite)

lemma sol_vars_back_intra:
  assumes solves: "terminates (declared_global p) p"
    and v: "(v, cx) ∈ sol_vars (declared_global p) p" and e: "(u, a, v) ∈ intra (prog_cfg p)"
  shows "(u, cx) ∈ sol_vars (declared_global p) p"
  by (rule sol_vars_dep_closed
        [OF solves v dep_L_routed_node_rhs_intra[OF prog_cfg_finite(1) e analysis_contribs_wf]])

lemma sol_vars_back_call_site:
  assumes solves: "terminates (declared_global p) p"
    and k: "(k, cx) ∈ sol_vars (declared_global p) p"
    and e: "(u, ca, FunctionEntry q, k) ∈ calls (prog_cfg p)"
  shows "(u, cx) ∈ sol_vars (declared_global p) p"
  by (rule sol_vars_dep_closed
        [OF solves k dep_L_routed_node_rhs_call_site
           [OF prog_cfg_finite(2) e dg_spec_wf_analysis_spec analysis_contribs_wf]])

lemma sol_vars_callee_result:
  assumes solves: "terminates (declared_global p) p"
    and k: "(k, cx) ∈ sol_vars (declared_global p) p"
    and e: "(u, ca, FunctionEntry q, k) ∈ calls (prog_cfg p)"
    and nb: "entry_of (declared_global p) p (call_info_of ca q)
               (dg_local (sol_env (declared_global p) p (Inl (u, cx))))
               (sol_global (declared_global p) p) ≠ Bot"
  shows "(FunctionResult q, ctx_succ (declared_global p) p u cx ca q)
           ∈ sol_vars (declared_global p) p"
proof -
  let ?d = "dg_local (sol_env (declared_global p) p (Inl (u, cx)))"
  let ?alt =
    "entry_alt (declared_global p) p (call_info_of ca q) ?d (sol_global (declared_global p) p)"
  have res: "q ∈ set (static_resolve (prog_cfg p) k u ca ?d)"
    using e prog_cfg_finite(2) by simp
  obtain D where deps: "enter_deps (enter♯ (analysis_spec (declared_global p) p) (call_info_of ca q))
      (mk_dg_man ?d global_of) (sol_env (declared_global p) p) [?alt] D"
    using place_enter_deps unfolding sol_global_def by blast
  have mem: "(fst ?alt, snd ?alt) ∈ set [?alt]" by simp
  show ?thesis
    unfolding ctx_succ_def entry_of_def
    by (rule sol_vars_dep_closed[OF solves k],
        rule dep_L_routed_node_rhs_callee_result[where resolve = "static_resolve (prog_cfg p)"
            and τ = "sol_env (declared_global p) p" and cx = cx,
          OF prog_cfg_finite(2) e deps mem res])
       (use nb in ‹simp add: entry_of_def›, rule dg_spec_wf_analysis_spec, rule
         analysis_contribs_wf)
qed

lemma sol_vars_back_reach:
  assumes solves: "terminates (declared_global p) p"
    and w: "(w, cx) ∈ sol_vars (declared_global p) p"
    and r: "local_reaches (prog_cfg p) y w"
  shows "(y, cx) ∈ sol_vars (declared_global p) p"
  using r[unfolded local_reaches_def]
proof (induction rule: converse_rtrancl_induct)
  case base show ?case by (rule w)
next
  case (step y z)
  from step.hyps(1) show ?case
    unfolding local_succ_rel_def
    using sol_vars_back_intra[OF solves step.IH] sol_vars_back_call_site[OF solves step.IH]
    by blast
qed
definition live_unknowns :: "imp_prog ⇒ (pp × 'c) set" where
  "live_unknowns p =
     {(u, cx) ∈ sol_vars (declared_global p) p.
        ∃q. prog_node (prog_table p) (prog_procs p) q u
          ∧ (FunctionResult q, cx) ∈ sol_vars (declared_global p) p}"

lemma live_unknowns_sub: "live_unknowns p ⊆ sol_vars (declared_global p) p"
  unfolding live_unknowns_def by blast

lemma live_unknowns_of_result:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and live: "prog_node (prog_table p) (prog_procs p) q y"
    and r: "(FunctionResult q, cx) ∈ sol_vars (declared_global p) p"
  shows "(y, cx) ∈ live_unknowns p"
proof -
  have "local_reaches (prog_cfg p) y (FunctionResult q)"
    unfolding prog_cfg_def by (rule prog_node_reaches[OF live])
  then have "(y, cx) ∈ sol_vars (declared_global p) p" by (rule sol_vars_back_reach[OF solves r])
  then show ?thesis unfolding live_unknowns_def using live r by blast
qed

theorem live_unknowns_cover:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "ctx_vars_cover_live (prog_cfg p) (λ_ _. True) (live_succ (declared_global p) p)
           root_ctx (live_unknowns p)"
proof (rule ctx_vars_cover_liveI)
  have root: "(FunctionResult prog_main_name, root_ctx) ∈ sol_vars (declared_global p) p"
    using pp_routed[OF solves] by (simp add: root_query_def prog_cfg_def)
  show "(cfg_entry (prog_cfg p), root_ctx) ∈ live_unknowns p"
    using live_unknowns_of_result[OF wf solves prog_node_main_entry[OF wf] root]
    by (simp add: prog_cfg_def)
next
  fix u a v ctx
  assume u: "(u, ctx) ∈ live_unknowns p" and e: "(u, a, v) ∈ intra (prog_cfg p)"
  from u obtain q where lq: "prog_node (prog_table p) (prog_procs p) q u"
    and r: "(FunctionResult q, ctx) ∈ sol_vars (declared_global p) p"
    unfolding live_unknowns_def by blast
  have "prog_node (prog_table p) (prog_procs p) q v"
    using prog_node_intra[OF wf lq] e by (simp add: prog_cfg_def)
  then show "(v, ctx) ∈ live_unknowns p" by (rule live_unknowns_of_result[OF wf solves _ r])
next
  fix u ctx dst fs as q k
  assume u: "(u, ctx) ∈ live_unknowns p"
    and e: "(u, CallEdge dst fs as, FunctionEntry q, k) ∈ calls (prog_cfg p)"
  from u obtain r where lr: "prog_node (prog_table p) (prog_procs p) r u"
    and res: "(FunctionResult r, ctx) ∈ sol_vars (declared_global p) p"
    unfolding live_unknowns_def by blast
  have "prog_node (prog_table p) (prog_procs p) r k"
    using prog_node_calls[OF wf lr] e by (simp add: prog_cfg_def)
  then show "(k, ctx) ∈ live_unknowns p" by (rule live_unknowns_of_result[OF wf solves _ res])
next
  fix u ctx dst fs as q k ctx'
  assume u: "(u, ctx) ∈ live_unknowns p"
    and e: "(u, CallEdge dst fs as, FunctionEntry q, k) ∈ calls (prog_cfg p)"
    and s: "live_succ (declared_global p) p u ctx (CallEdge dst fs as) q = Some ctx'"
  from u obtain r where lr: "prog_node (prog_table p) (prog_procs p) r u"
    and res: "(FunctionResult r, ctx) ∈ sol_vars (declared_global p) p"
    unfolding live_unknowns_def by blast
  have "prog_node (prog_table p) (prog_procs p) r k"
    using prog_node_calls[OF wf lr] e by (simp add: prog_cfg_def)
  then have k: "(k, ctx) ∈ sol_vars (declared_global p) p"
    using live_unknowns_of_result[OF wf solves _ res] live_unknowns_sub by blast
  from s have nb: "entry_of (declared_global p) p (call_info_of (CallEdge dst fs as) q)
               (dg_local (sol_env (declared_global p) p (Inl (u, ctx))))
               (sol_global (declared_global p) p) ≠ Bot"
    and c': "ctx' = ctx_succ (declared_global p) p u ctx (CallEdge dst fs as) q"
    by (auto simp: live_succ_def split: if_splits)
  have rq: "(FunctionResult q, ctx') ∈ sol_vars (declared_global p) p"
    unfolding c' by (rule sol_vars_callee_result[OF solves k e nb])
  have "prog_node (prog_table p) (prog_procs p) q (FunctionEntry q)"
    using prog_node_callee_entry[OF wf] e by (simp add: prog_cfg_def)
  then show "(FunctionEntry q, ctx') ∈ live_unknowns p"
    by (rule live_unknowns_of_result[OF wf solves _ rq])
qed

lemma root_live_key:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "root_query p ∈ live_unknowns p"
proof -
  have r: "(FunctionResult prog_main_name, root_ctx) ∈ sol_vars (declared_global p) p"
    using pp_routed[OF solves] by (simp add: root_query_def prog_cfg_def)
  show ?thesis
    using live_unknowns_of_result[OF wf solves prog_node_result[OF prog_node_main_entry[OF wf]] r]
    by (simp add: root_query_def prog_cfg_def)
qed
subsection ‹The entry-state endpoint over the live keys›

text ‹
  The routed soundness statement restated over ‹live_unknowns›, whose coverage
  needs no premise beyond termination, and carried to the published table and
  the source-level endpoints under the functional context policy.
›

context
  fixes p :: imp_prog
begin

interpretation dg_base: analysis_contract "analysis_spec (declared_global p) p"
    "λd e. pgam p d e" "declared_global p"
  by (rule place_contract)

text ‹
  Establishes ‹routed_analysis› over the live keys, for any relation admitting call contexts.
  Beyond termination and well-formedness a policy owes only its own two facts: an
  admitted context is the one this pipeline routes the entered state to, and every
  concrete call at a live key is admitted somewhere. ‹live_unknowns_cover› supplies
  callee-entry membership.
›

lemma routed_analysis_from_live_unknowns:
  fixes adm :: "'c context_policy"
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and cover_R: "⋀u ctx dst pars args q cont s ctx'. (u, ctx) ∈ live_unknowns p
        ⟹ (u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)
        ⟹ ctx' ∈ adm u ctx (call_info_of (CallEdge dst pars args) q) s
              (call_enter (declared_global p) (CallEdge dst pars args) s)
        ⟹ route (declared_global p) u ctx
              (entered p (call_info_of (CallEdge dst pars args) q)
                 (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))))
              (CallEdge dst pars args) = ctx'"
    and total_R: "⋀u ctx dst pars args q cont s. (u, ctx) ∈ live_unknowns p
        ⟹ (u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)
        ⟹ s ∈ pgam p (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))) (gsol p)
        ⟹ adm u ctx (call_info_of (CallEdge dst pars args) q) s
                      (call_enter (declared_global p) (CallEdge dst pars args) s) ≠ {}"
  shows "routed_analysis (analysis_spec (declared_global p) p) (λd e. pgam p d e)
     (declared_global p) (prog_cfg p) buffer_key global_of
     (route (declared_global p)) Bot (Lifted init_st)
     (place_rg (declared_global p) (Lifted init_st))
     (sol_env (declared_global p) p) (live_unknowns p) (root_query p) seed (λd. d = Bot) adm
     (λd e. map_lift (rd (declared_global p)) (place_cmb p d e))
     γV emptyV classify"
proof (unfold_locales, goal_cases CmbWf ExtraWf FinE PP SgCov SgUncov Fwd FinC CallsUnique
    SeedUnknown SeedNeGlobal IsBotBot IsBotSound ResolveSound EnterCover EnterTotal CombFwd GammaRd
    EmptyExact ClProved ClRefuted VarsFin)
  case CmbWf show ?case by (rule sp_wf_routed_call_program[OF dg_spec_wf_analysis_spec])
next
  case ExtraWf then show ?case by (rule sp_wf_routed_entry_seed_programs)
next
  case FinE show ?case unfolding prog_cfg_def by (simp add: compile_prog_finite)
next
  case PP show ?case
    by (rule post_bounded_subset[OF post_bounded_of_part_post_solution[OF pp_routed[OF solves]]
          root_live_key[OF wf solves] live_unknowns_sub])
next
  case (SgCov v c) then show ?case by simp
next
  case (SgUncov v c) then show ?case by simp
next
  case (Fwd u a v c)
  show ?case
    by (rule ctx_vars_cover_live_edgeD[OF live_unknowns_cover[OF wf solves] Fwd(1) _ Fwd(3)]) simp
next
  case FinC show ?case unfolding prog_cfg_def by (simp add: compile_prog_finite)
next
  case CallsUnique show ?case
    unfolding calls_source_unique_def prog_cfg_def
    using compile_prog_calls_source_unique by blast
next
  case (SeedUnknown q ctx) show ?case by (rule seed_ne_buffer_key)
next
  case (SeedNeGlobal q ctx v) show ?case by (rule seed_ne_global_of)
next  case IsBotBot show ?case by simp
next
  case (IsBotSound d g') then show ?case by simp
next
  case (ResolveSound u ctx dst pars args q cont s)
  then show ?case unfolding prog_cfg_def by (simp add: compile_prog_finite)
next
  case (EnterCover u ctx dst pars args q cont s ctx')
  let ?ci = "call_info_of (CallEdge dst pars args) q"
  let ?caller = "dg_local (sol_env (declared_global p) p (Inl (u, ctx)))"
  let ?g = "genv global_of (sol_env (declared_global p) p)"
  let ?ent = "entered p ?ci ?caller"
  have sin: "s ∈ pgam p ?caller (gsol p)"
    using EnterCover(3) by (simp add: sol_global_def)
  have cov: "entry_pairs_cover (λd. pgam p d (gsol p)) s
      (call_enter (declared_global p) (CallEdge dst pars args) s)
      [entry_alt (declared_global p) p ?ci ?caller (gsol p)]"
  proof (rule entry_cover[OF solves _ EnterCover(2) sin])
    show "(cont, ctx) ∈ sol_vars (declared_global p) p"
      using ctx_vars_cover_live_combineD[OF live_unknowns_cover[OF wf solves] EnterCover(1,2)]
        live_unknowns_sub by blast
  qed
  obtain c e where ce: "entry_alt (declared_global p) p ?ci ?caller (gsol p) = (c, e)"
    by (cases "entry_alt (declared_global p) p ?ci ?caller (gsol p)")
  have nbE: "?ent ≠ Bot"
  proof
    assume "?ent = Bot"
    with cov ce show False by (simp add: entry_pairs_cover_def entry_of_def place_cmb_bot)
  qed
  have req: "route (declared_global p) u ctx ?ent (CallEdge dst pars args) = ctx'"
    by (rule cover_R[OF EnterCover(1,2,4)])
  have covE: "(FunctionEntry q, ctx') ∈ live_unknowns p"
    by (rule ctx_vars_cover_live_enterD[OF live_unknowns_cover[OF wf solves] EnterCover(1,2)])
       (use nbE req in ‹simp add: live_succ_def ctx_succ_def›)
  obtain pub where runs: "enter_runs (enter♯ (analysis_spec (declared_global p) p) ?ci)
      (mk_dg_man ?caller global_of) (sol_env (declared_global p) p)
      [entry_alt (declared_global p) p ?ci ?caller ?g] pub"
    using place_enter_runs by blast
  obtain deps where deps: "enter_deps (enter♯ (analysis_spec (declared_global p) p) ?ci)
      (mk_dg_man ?caller global_of) (sol_env (declared_global p) p)
      [entry_alt (declared_global p) p ?ci ?caller ?g] deps"
    using place_enter_deps by blast
  have ce': "entry_alt (declared_global p) p ?ci ?caller ?g = (c, e)"
    using ce by (simp add: sol_global_def)
  have sc: "s ∈ pgam p c ?g"
    and ec: "call_enter (declared_global p) (CallEdge dst pars args) s ∈ pgam p e ?g"
    using cov ce by (simp_all add: entry_pairs_cover_def sol_global_def)
  have re: "route (declared_global p) u ctx e (CallEdge dst pars args) = ctx'"
    using req ce by (simp add: entry_of_def)
  show ?case
    using runs deps sc ec re covE unfolding ce' by fastforce
next
  case (EnterTotal u ctx dst pars args q cont s)
  then show ?case by (intro total_R) (simp_all add: sol_global_def)
next
  case (CombFwd cl c1 dst pars args q cont)
  show ?case
    by (rule ctx_vars_cover_live_combineD[OF live_unknowns_cover[OF wf solves] CombFwd(1,2)])
next
  case (GammaRd d g') show ?case by simp
next
  case (EmptyExact v) then show ?case by (rule emptyV_soundD)
next
  case (ClProved c d s) then show ?case by (rule classify_proved)
next
  case (ClRefuted c d s) then show ?case by (rule classify_refuted)
next
  case VarsFin show ?case
    by (rule finite_subset[OF live_unknowns_sub vars_finite_of_terminates[OF solves]])
qed

text ‹
  The reader over the live keys agrees with the published one on them and describes
  nothing elsewhere, so a bound proved for it is a bound on the published table.
›

lemma gamma_live_reader_le:
  "pgam p (solved_local_reader (live_unknowns p) (sol_env (declared_global p) p) (Inl (v, ctx)))
       (gsol p)
     ⊆ pgam p (reader (declared_global p) p (Inl (v, ctx))) (gsol p)"
proof (cases "(v, ctx) ∈ live_unknowns p")
  case True
  then have "(v, ctx) ∈ sol_vars (declared_global p) p" by (rule subsetD[OF live_unknowns_sub])
  with True show ?thesis by (simp add: reader_def)
next
  case False then show ?thesis by (simp add: place_cmb_bot)
qed

theorem activation_collect_sound_live_unknowns:
  fixes adm :: "'c context_policy"
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and cover_R: "⋀u ctx dst pars args q cont s ctx'. (u, ctx) ∈ live_unknowns p
        ⟹ (u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)
        ⟹ ctx' ∈ adm u ctx (call_info_of (CallEdge dst pars args) q) s
              (call_enter (declared_global p) (CallEdge dst pars args) s)
        ⟹ route (declared_global p) u ctx
              (entered p (call_info_of (CallEdge dst pars args) q)
                 (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))))
              (CallEdge dst pars args) = ctx'"
    and total_R: "⋀u ctx dst pars args q cont s. (u, ctx) ∈ live_unknowns p
        ⟹ (u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)
        ⟹ s ∈ pgam p (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))) (gsol p)
        ⟹ adm u ctx (call_info_of (CallEdge dst pars args) q) s
                      (call_enter (declared_global p) (CallEdge dst pars args) s) ≠ {}"
  shows "𝒜⇘declared_global p,adm,root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ pgam p (reader (declared_global p) p (Inl (v, ctx))) (gsol p)"
proof -
  interpret live: routed_analysis "analysis_spec (declared_global p) p" "λd e. pgam p d e"
      "declared_global p" "prog_cfg p" buffer_key global_of
      "route (declared_global p)" Bot
        "Lifted init_st" "place_rg (declared_global p) (Lifted init_st)"
      "sol_env (declared_global p) p" "live_unknowns p" "root_query p" seed "λd. d = Bot" adm
      "λd e. map_lift (rd (declared_global p)) (place_cmb p d e)"
      γV emptyV classify
    by (rule routed_analysis_from_live_unknowns[where adm = adm, OF wf solves cover_R total_R])
  have entry_cov: "(cfg_entry (prog_cfg p), root_ctx) ∈ live_unknowns p"
    by (rule ctx_vars_cover_live_entryD[OF live_unknowns_cover[OF wf solves]])
  have init_env_le:
    "init_env (declared_global p) p ≤ genv global_of (sol_env (declared_global p) p)"
    using init_env_le_sol[OF solves subsetD[OF live_unknowns_sub entry_cov]
      live.pp_entry_init_global_bound[OF entry_cov]]
    by (simp add: sol_global_def)
  have "𝒜⇘declared_global p,adm,root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
        ⊆ pgam p (solved_local_reader (live_unknowns p) (sol_env (declared_global p) p) (Inl (v, ctx)))
             (gsol p)"
    using live.activation_collect_dg_sound
        [OF entry_cov _ init_env_le, of "cinit_stores (declared_global p)" v ctx] cinit_le_init
    by (simp add: sol_global_def)
  then show ?thesis using gamma_live_reader_le by blast
qed

text ‹
  A policy whose route ignores the entered state --- a call string, or the unit
  context --- admits exactly the context it computes, so both of its facts are
  immediate.
›

theorem fun_route_activation_collect_sound_of_terminates:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c"
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
           prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ pgam p (reader (declared_global p) p (Inl (v, ctx))) (gsol p)"
proof (rule activation_collect_sound_live_unknowns[OF wf solves])
  fix u ctx dst pars args q cont and s :: store and ctx'
  assume "(u, ctx) ∈ live_unknowns p"
    and "(u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)"
    and Rc: "ctx' ∈ context_policy_of_fun ctx_fun u ctx (call_info_of (CallEdge dst pars args) q) s
               (call_enter (declared_global p) (CallEdge dst pars args) s)"
  show "route (declared_global p) u ctx
          (entered p (call_info_of (CallEdge dst pars args) q)
             (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))))
          (CallEdge dst pars args) = ctx'"
    using Rc
    by (simp add:
      route_const[of _ _ _ _ "call_enter (declared_global p) (CallEdge dst pars args) s"])
next
  fix u ctx dst pars args q cont and s :: store
  show "context_policy_of_fun ctx_fun u ctx (call_info_of (CallEdge dst pars args) q) s
                 (call_enter (declared_global p) (CallEdge dst pars args) s) ≠ {}"
    by simp
qed

text ‹
  The same fact read off the published surface, and what it yields at a program
  point, at a check and at a source run. A store reaching a point lies in one of
  the point's contexts, so the point is bounded by the union of the states published
  there. At the unit policy the union has the one member ‹()›.
›

theorem fun_route_state_at_sound:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c"
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
           prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ γV (state_at (declared_global p) p ctx v)"
proof -
  have "𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
          prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
        ⊆ gamma_lift γV (lookup_table (result (declared_global p) p) v ctx)"
    using fun_route_activation_collect_sound_of_terminates[OF route_const wf solves]
    unfolding gamma_reader_eq_lookup .
  then show ?thesis
    by (auto simp: state_at_unfold bot_state_empty split: lifted.splits)
qed

theorem fun_route_result_node_sound:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c"
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒞⇘declared_global p,prog_cfg p,cinit_stores (declared_global p)⇙ v
           ⊆ (⋃ctx. γV (state_at (declared_global p) p ctx v))"
  using fun_route_node_collect_eq_Union[where ctx_fun = ctx_fun and p = p and v = v]
    fun_route_state_at_sound[OF route_const wf solves]
  by (simp add: SUP_mono')

theorem fun_route_report_proved_sound:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c"
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and mem: "(v, c, Check_Proved) ∈ set (report (declared_global p) p ctx)"
  shows "∀s ∈ 𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
                 prog_cfg p,cinit_stores (declared_global p)⇙ v ctx. truthy (⟦c⟧e s)"
proof -
  have fin: "finite (intra (prog_cfg p))"
    unfolding prog_cfg_def by (simp add: compile_prog_finite)
  show ?thesis
    by (rule classify_checks_proved_sound
          [where g = "prog_cfg p" and env = "state_at (declared_global p) p ctx"
             and classify = classify and γS = γV
             and reach = "λv. 𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
                               prog_cfg p,cinit_stores (declared_global p)⇙ v ctx",
           OF fin _ classify_proved fun_route_state_at_sound[OF route_const wf solves]])
       (use mem in ‹simp add: report_def state_at_def analysis_surface.report_def›)
qed

theorem fun_route_report_refuted_sound:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c"
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and mem: "(v, c, Check_Refuted) ∈ set (report (declared_global p) p ctx)"
  shows "∀s ∈ 𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
                 prog_cfg p,cinit_stores (declared_global p)⇙ v ctx. ¬ truthy (⟦c⟧e s)"
proof -
  have fin: "finite (intra (prog_cfg p))"
    unfolding prog_cfg_def by (simp add: compile_prog_finite)
  show ?thesis
    by (rule classify_checks_refuted_sound
          [where g = "prog_cfg p" and env = "state_at (declared_global p) p ctx"
             and classify = classify and γS = γV
             and reach = "λv. 𝒜⇘declared_global p,context_policy_of_fun ctx_fun,root_ctx,
                               prog_cfg p,cinit_stores (declared_global p)⇙ v ctx",
           OF fin _ classify_refuted fun_route_state_at_sound[OF route_const wf solves]])
       (use mem in ‹simp add: report_def state_at_def analysis_surface.report_def›)
qed

theorem fun_route_source_sound:
  fixes ctx_fun :: "cfg_node ⇒ 'c ⇒ store ⇒ 'c" and s0 s :: store
  assumes route_const: "⋀u ctx d ca s. route (declared_global p) u ctx d ca = ctx_fun u ctx s"
    and wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and s0: "s0 ∈ cinit_stores (declared_global p)"
    and run: "declared_global p, prog_table p ⊢ (main_body (prog_table p), s0, [])
                →p* (residual, s, frs)"
  shows "∃v stk ctx. prog_table p, prog_cfg p, prog_main_name ⊢ (residual, s, frs) ≈ (v, s, stk)
           ∧ s ∈ γV (state_at (declared_global p) p ctx v)"
proof -
  have cfg_eq: "prog_cfg p = compile_prog (prog_table p) (prog_procs p)"
    by (rule prog_cfg_def)
  have "∃v stk. prog_table p, prog_cfg p, prog_main_name ⊢ (residual, s, frs) ≈ (v, s, stk)
          ∧ s ∈ (⋃ctx. γV (state_at (declared_global p) p ctx v))"
    unfolding cfg_eq
    by (rule source_sound_from_node_collect_cap[OF wf s0 run])
       (use fun_route_result_node_sound[OF route_const wf solves] in ‹simp add: cfg_eq›)
  then show ?thesis by blast
qed

subsection ‹Entry-state routing›

text ‹
  Instantiates the live-key endpoints at ‹admitted_contexts›, the policy that routes
  a call by its entered state. Its two policy facts hold by construction, so the
  results need only termination and a well-formed program.
›

lemma entry_state_routed_analysis_from_live_unknowns:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "routed_analysis (analysis_spec (declared_global p) p) (λd e. pgam p d e)
     (declared_global p) (prog_cfg p) buffer_key global_of
     (route (declared_global p)) Bot (Lifted init_st)
     (place_rg (declared_global p) (Lifted init_st))
     (sol_env (declared_global p) p) (live_unknowns p) (root_query p) seed (λd. d = Bot)
     (admitted_contexts (declared_global p) p)
     (λd e. map_lift (rd (declared_global p)) (place_cmb p d e))
     γV emptyV classify"
proof (rule routed_analysis_from_live_unknowns[OF wf solves])
  fix u ctx dst pars args q cont and s :: store and ctx'
  assume "(u, ctx) ∈ live_unknowns p"
    and "(u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)"
    and Rc: "ctx' ∈ admitted_contexts (declared_global p) p u ctx
               (call_info_of (CallEdge dst pars args) q) s
               (call_enter (declared_global p) (CallEdge dst pars args) s)"
  let ?ci = "call_info_of (CallEdge dst pars args) q"
  let ?caller = "dg_local (sol_env (declared_global p) p (Inl (u, ctx)))"
  let ?ent = "entered p ?ci ?caller"
  from Rc[unfolded admitted_contexts_alt] obtain cont' entry
    where mem: "(cont', entry) ∈ set [entry_alt (declared_global p) p ?ci ?caller (gsol p)]"
      and req0: "ctx' = route (declared_global p) u ctx entry
                   (CallEdge (ci_dst ?ci) (ci_formals ?ci) (ci_args ?ci))"
    by (rule routed_entry_context_relE)
  have "entry = ?ent" using mem by (simp add: entry_of_def) (metis snd_conv)
  then show "route (declared_global p) u ctx ?ent (CallEdge dst pars args) = ctx'"
    using req0 by simp
next
  fix u ctx dst pars args q cont and s :: store
  assume uL: "(u, ctx) ∈ live_unknowns p"
    and ce: "(u, CallEdge dst pars args, FunctionEntry q, cont) ∈ calls (prog_cfg p)"
    and sin: "s ∈ pgam p (dg_local (sol_env (declared_global p) p (Inl (u, ctx)))) (gsol p)"
  have cont: "(cont, ctx) ∈ sol_vars (declared_global p) p"
    using ctx_vars_cover_live_combineD[OF live_unknowns_cover[OF wf solves] uL ce]
      live_unknowns_sub by blast
  show "admitted_contexts (declared_global p) p u ctx
                 (call_info_of (CallEdge dst pars args) q) s
                 (call_enter (declared_global p) (CallEdge dst pars args) s) ≠ {}"
    unfolding admitted_contexts_alt
    by (rule routed_entry_context_rel_total)
       (use entry_cover[OF solves cont ce sin]
         in ‹simp add: sol_global_def del: declared_global_iff›)
qed

theorem entry_state_activation_collect_sound_of_terminates:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒜⇘declared_global p,admitted_contexts (declared_global p) p,
           root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ pgam p (reader (declared_global p) p (Inl (v, ctx))) (gsol p)"
proof -
  interpret live: routed_analysis "analysis_spec (declared_global p) p" "λd e. pgam p d e"
      "declared_global p" "prog_cfg p" buffer_key global_of
      "route (declared_global p)" Bot
        "Lifted init_st" "place_rg (declared_global p) (Lifted init_st)"
      "sol_env (declared_global p) p" "live_unknowns p" "root_query p" seed "λd. d = Bot"
      "admitted_contexts (declared_global p) p"
      "λd e. map_lift (rd (declared_global p)) (place_cmb p d e)"
      γV emptyV classify
    by (rule entry_state_routed_analysis_from_live_unknowns[OF wf solves])
  have entry_cov: "(cfg_entry (prog_cfg p), root_ctx) ∈ live_unknowns p"
    by (rule ctx_vars_cover_live_entryD[OF live_unknowns_cover[OF wf solves]])
  have init_env_le:
    "init_env (declared_global p) p ≤ genv global_of (sol_env (declared_global p) p)"
    using init_env_le_sol[OF solves subsetD[OF live_unknowns_sub entry_cov]
      live.pp_entry_init_global_bound[OF entry_cov]]
    by (simp add: sol_global_def)
  have "𝒜⇘declared_global p,admitted_contexts (declared_global p) p,
          root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
        ⊆ pgam p (solved_local_reader (live_unknowns p) (sol_env (declared_global p) p) (Inl (v, ctx)))
             (gsol p)"
    using live.activation_collect_dg_sound
        [OF entry_cov _ init_env_le, of "cinit_stores (declared_global p)" v ctx] cinit_le_init
    by (simp add: sol_global_def)
  then show ?thesis using gamma_live_reader_le by blast
qed

corollary entry_state_lookup_sound_of_terminates:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒜⇘declared_global p,admitted_contexts (declared_global p) p,
           root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ gamma_lift γV (lookup_table (result (declared_global p) p) v ctx)"
  using entry_state_activation_collect_sound_of_terminates[OF wf solves]
  unfolding gamma_reader_eq_lookup .

lemma entry_state_has_context_of_terminates:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
    and t: "t ∈ 𝒯⇘declared_global p,prog_cfg p,cinit_stores (declared_global p)⇙"
  shows "∃c. activation_context_rel (declared_global p) (admitted_contexts (declared_global p) p)
               root_ctx (prog_cfg p) t c"
proof -
  interpret live: routed_analysis "analysis_spec (declared_global p) p" "λd e. pgam p d e"
      "declared_global p" "prog_cfg p" buffer_key global_of
      "route (declared_global p)" Bot
        "Lifted init_st" "place_rg (declared_global p) (Lifted init_st)"
      "sol_env (declared_global p) p" "live_unknowns p" "root_query p" seed "λd. d = Bot"
      "admitted_contexts (declared_global p) p"
      "λd e. map_lift (rd (declared_global p)) (place_cmb p d e)"
      γV emptyV classify
    by (rule entry_state_routed_analysis_from_live_unknowns[OF wf solves])
  have entry_cov: "(cfg_entry (prog_cfg p), root_ctx) ∈ live_unknowns p"
    by (rule ctx_vars_cover_live_entryD[OF live_unknowns_cover[OF wf solves]])
  have init_env_le:
    "init_env (declared_global p) p ≤ genv global_of (sol_env (declared_global p) p)"
    using init_env_le_sol[OF solves subsetD[OF live_unknowns_sub entry_cov]
      live.pp_entry_init_global_bound[OF entry_cov]]
    by (simp add: sol_global_def)
  show ?thesis
    using live.routed_valid_activation_trace_has_context[OF entry_cov _ init_env_le t] cinit_le_init
    by simp
qed

theorem entry_state_node_collect_eq_Union_of_terminates:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "𝒞⇘declared_global p,prog_cfg p,cinit_stores (declared_global p)⇙ v
           = (⋃ctx. 𝒜⇘declared_global p,admitted_contexts (declared_global p) p,
                       root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx)"
  by (rule node_collect_eq_Union_activation_of_has_context)
     (rule entry_state_has_context_of_terminates[OF wf solves])

text ‹
  The entry-state policy in one statement: the solve is fixed first, the policy is
  read off it, and that policy is total on valid traces and covered by the solved
  states. This order of quantifiers is why reading contexts off the result is not
  circular.
›
corollary entry_state_policy_of_terminates:
  assumes wf: "wf_program_compile_input p" and solves: "terminates (declared_global p) p"
  shows "∀t ∈ 𝒯⇘declared_global p,prog_cfg p,cinit_stores (declared_global p)⇙.
           ∃c. activation_context_rel (declared_global p) (admitted_contexts (declared_global p) p)
                 root_ctx (prog_cfg p) t c"
    and "𝒜⇘declared_global p,admitted_contexts (declared_global p) p,
           root_ctx,prog_cfg p,cinit_stores (declared_global p)⇙ v ctx
           ⊆ pgam p (reader (declared_global p) p (Inl (v, ctx))) (gsol p)"
  using entry_state_has_context_of_terminates[OF wf solves]
    entry_state_activation_collect_sound_of_terminates[OF wf solves]
  by blast+

end

end

end