Theory DG_Keyed_Split_Spec
theory DG_Keyed_Split_Spec
imports DG_Local_State_Spec "Voblint_Solver.Strategy_Tree_Side_Buffering"
"Voblint_Solver.Strategy_Program_Fold"
begin
section ‹One solver unknown per global name›
text ‹
The ownership-split lifter of ‹DG_Ownership_Split_Spec› keeps every program
global in one shared value. Reading a global therefore depends on all of them,
and writing one wakes every reader of any other. Goblint keys its globals by
name instead, and this theory does the same for a whole-state component.
A global name ‹x› lives at its own unknown. Its value there is a global half
that describes ‹x› alone, cut out of a whole state by ‹rg x›. A transfer
reads only the names its edge mentions and publishes only the names its edge
can write. Every other global it hands the wrapped transfer as ‹free›, a global
half that claims nothing about the unread names, so the wrapped transfer stays
sound on it. Its result says nothing useful about the unread names either, and
it is not published: the next point reads them from their own unknowns again.
That last step is sound because the edge leaves the unpublished globals of a
concrete store unchanged. Which names an edge may change is a fact of the
concrete semantics (‹edge_writes›), established in ‹CFG_Transfer›. Which names it reads only
affects precision: a name left out is read as ‹free›.
›
subsection ‹Reading and publishing named globals›
text ‹
Reading a list of names folds their global halves into an accumulator, each
value cut to the name it was read at. Publishing a list of names sends each
its own cut of one whole state. The value-level counterparts are
‹view_of› and ‹pub_sides›.
›
fun read_view ::
"('x,'k,vname,'d::semilattice_sup,'d) man ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ vname list ⇒ 'd
⇒ ('x,'k,('d,'d) dg_state,'d) strategy_program"
where
"read_view m rg [] acc = sp_return acc"
| "read_view m rg (x # xs) acc = man_global m x ⤜ (λg. read_view m rg xs (rg x g ⊔ acc))"
fun publish_at ::
"('x,'k,vname,'d,'d) man ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ vname list ⇒ 'd
⇒ ('x,'k,('d,'d) dg_state,unit) strategy_program"
where
"publish_at m rg [] r = sp_return ()"
| "publish_at m rg (x # xs) r = man_sideg m x (rg x r) ⤜ (λ_. publish_at m rg xs r)"
definition view_of :: "(vname ⇒ 'd ⇒ 'd) ⇒ vname list ⇒ (vname ⇒ 'd) ⇒ 'd
⇒ 'd::semilattice_sup" where
"view_of rg xs e acc = fold (λx acc. rg x (e x) ⊔ acc) xs acc"
text ‹The global half a whole environment describes, over a list of names.›
definition full_view :: "(vname ⇒ 'd ⇒ 'd) ⇒ vname list ⇒ (vname ⇒ 'd)
⇒ 'd::bounded_semilattice_sup_bot" where
"full_view rg xs e = view_of rg xs e bot"
definition pub_sides :: "(vname ⇒ 'k) ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ vname list ⇒ 'd
⇒ 'x + 'k ⇒ ('d::bounded_semilattice_sup_bot, 'd) dg_state" where
"pub_sides key rg xs r = foldr (λx acc. acc ⊔ bot(Inr (key x) := DG bot (rg x r))) xs bot"
lemma view_of_ge_acc: "acc ≤ view_of rg xs e acc"
unfolding view_of_def
by (induction xs arbitrary: acc) (auto intro: order_trans[OF sup_ge2])
lemma view_of_ge_mem: "x ∈ set xs ⟹ rg x (e x) ≤ view_of rg xs e acc"
unfolding view_of_def
proof (induction xs arbitrary: acc)
case Nil then show ?case by simp
next
case (Cons y ys)
show ?case
proof (cases "x = y")
case True
then show ?thesis
using view_of_ge_acc[of "rg y (e y) ⊔ acc" rg ys e]
by (simp add: view_of_def)
next
case False
with Cons show ?thesis by simp
qed
qed
lemma view_of_le:
assumes "acc ≤ v" and "⋀x. x ∈ set xs ⟹ rg x (e x) ≤ v"
shows "view_of rg xs e acc ≤ v"
using assms unfolding view_of_def by (induction xs arbitrary: acc) auto
lemma full_view_mono:
assumes rg_mono: "⋀x v v'. v ≤ v' ⟹ rg x v ≤ rg x v'"
and le: "⋀x. e x ≤ e' x"
shows "full_view rg xs e ≤ full_view rg xs e'"
unfolding full_view_def
proof (rule view_of_le)
fix x assume "x ∈ set xs"
then show "rg x (e x) ≤ view_of rg xs e' bot"
using rg_mono[OF le[of x], of x] view_of_ge_mem[of x xs rg e' bot] by (blast intro: order_trans)
qed simp
lemma pub_sides_ge:
"x ∈ set xs ⟹ DG bot (rg x r) ≤ pub_sides key rg xs r (Inr (key x))"
unfolding pub_sides_def
by (induction xs) (auto simp: sup_fun_def intro: le_supI1 le_supI2)
subsubsection ‹What a solver observes of them›
text ‹Reading names is observed as a dependency on each of their unknowns and
nothing else; publishing names is observed as one side effect per name.›
lemma traverse_read_view [simp]:
"traverse_rhs (read_view (mk_dg_man d key) rg xs acc K) τ
= traverse_rhs (K (view_of rg xs (genv key τ) acc)) τ"
by (induction xs arbitrary: acc) (simp_all add: view_of_def sp_bind_def sp_return_def)
lemma sides_read_view [simp]:
"sides_of_rhs (read_view (mk_dg_man d key) rg xs acc K) τ
= sides_of_rhs (K (view_of rg xs (genv key τ) acc)) τ"
by (induction xs arbitrary: acc) (simp_all add: view_of_def sp_bind_def sp_return_def)
lemma dep_aux_read_view [simp]:
"dep_aux τ (read_view (mk_dg_man d key) rg xs acc K)
= (Inr ∘ key) ` set xs ∪ dep_aux τ (K (view_of rg xs (genv key τ) acc))"
by (induction xs arbitrary: acc) (auto simp: view_of_def sp_bind_def sp_return_def)
lemma traverse_publish_at [simp]:
"traverse_rhs (publish_at (mk_dg_man d key) rg xs r K) τ = traverse_rhs (K ()) τ"
by (induction xs) (simp_all add: sp_bind_def sp_return_def)
lemma sides_publish_at [simp]:
"sides_of_rhs (publish_at (mk_dg_man d key) rg xs r K) τ
= sides_of_rhs (K ()) τ ⊔ pub_sides key rg xs r"
by (induction xs) (simp_all add: pub_sides_def sp_bind_def sp_return_def ac_simps)
lemma dep_aux_publish_at [simp]:
"dep_aux τ (publish_at (mk_dg_man d key) rg xs r K) = dep_aux τ (K ())"
by (induction xs) (simp_all add: sp_bind_def sp_return_def)
lemma side_path_read_view [simp]:
"side_path τ (read_view (mk_dg_man d key) rg xs acc K)
= side_path τ (K (view_of rg xs (genv key τ) acc))"
by (induction xs arbitrary: acc)
(simp_all add: view_of_def dg_read_global_def sp_read_global_def sp_bind_def sp_return_def)
lemma side_path_publish_at [simp]:
"side_path τ (publish_at (mk_dg_man d key) rg xs r K)
= map key xs @ side_path τ (K ())"
by (induction xs) (simp_all add: dg_sideg_def sp_publish_def sp_bind_def sp_return_def)
lemma sp_wf_read_view [intro]:
"(⋀x. sp_wf (man_global m x)) ⟹ sp_wf (read_view m rg xs acc)"
by (induction xs arbitrary: acc) (auto intro!: sp_wf_bind)
lemma sp_wf_publish_at [intro]:
"(⋀x v. sp_wf (man_sideg m x v)) ⟹ sp_wf (publish_at m rg xs r)"
by (induction xs) (auto intro!: sp_wf_bind)
subsection ‹The keyed transfers›
text ‹
Each transfer reads its names, recombines them with the point's local half,
runs the wrapped whole-state operation, publishes the names it may change and
answers with the local half of the result. ‹cmb› and ‹rl› are the binary
recombination and local projection the ownership split already uses.
›
definition keyed_transfer ::
"('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ (vname list ⇒ 'd)
⇒ vname list ⇒ vname list ⇒ ('d ⇒ 'd)
⇒ ('x,'k,vname,'d::bounded_semilattice_sup_bot,'d) man_transfer"
where
"keyed_transfer cmb rl rg free R W f m =
read_view m rg R (free R) ⤜ (λg.
publish_at m rg W (f (cmb (man_local m) g)) ⤜ (λ_.
sp_return (rl (f (cmb (man_local m) g)))))"
lemma dep_keyed_transfer:
"dep_program τ (transfer_program (keyed_transfer cmb rl rg free R W f) src key)
= insert src ((Inr ∘ key) ` set R)"
by (simp add: dep_transfer_program keyed_transfer_def
sp_compile_with_def sp_bind_def sp_return_def)
lemma side_path_keyed_transfer:
"side_path τ (sp_compile
(transfer_program (keyed_transfer cmb rl rg free R W f) src key)) = map key W"
by (cases src)
(simp_all add: transfer_program_def transfer_program_at_def keyed_transfer_def
sp_compile_def sp_compile_with_def sp_map_def sp_bind_def sp_return_def
dg_read_at_def sp_read_local_def sp_read_global_def)
definition keyed_combine_transfer ::
"('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ (vname list ⇒ 'd)
⇒ vname list ⇒ ('d ⇒ 'd ⇒ 'd)
⇒ ('x,'k,vname,'d::bounded_semilattice_sup_bot,'d) man_combine_transfer"
where
"keyed_combine_transfer cmb rl rg free W h m de =
publish_at m rg W (h (cmb (man_local m) (free [])) (cmb de (free []))) ⤜ (λ_.
sp_return (rl (h (cmb (man_local m) (free [])) (cmb de (free [])))))"
definition keyed_enter_transfer ::
"('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ (vname ⇒ 'd ⇒ 'd) ⇒ (vname list ⇒ 'd)
⇒ vname list ⇒ vname list ⇒ ('d ⇒ 'd enter_result list)
⇒ ('x,'k,vname,'d::bounded_semilattice_sup_bot,'d) man_enter_transfer"
where
"keyed_enter_transfer cmb rl rg free R W en m =
read_view m rg R (free R) ⤜ (λg.
publish_at m rg W (⨆(c, e)←en (cmb (man_local m) g). e) ⤜ (λ_.
sp_return (map (λ(c, e). (rl c, rl e)) (en (cmb (man_local m) g)))))"
text ‹A call answers the alternatives its entry computes from the recombined state
and publishes each name in ‹W› cut from the join of the entered states.›
lemma enter_runs_keyed_enter_transfer:
"enter_runs (keyed_enter_transfer cmb rl rg free R W en) (mk_dg_man d key) σ
(map (λ(c, e). (rl c, rl e)) (en (cmb d (view_of rg R (genv key σ) (free R)))))
(pub_sides key rg W
(⨆(c, e)←en (cmb d (view_of rg R (genv key σ) (free R))). e))"
unfolding enter_runs_def keyed_enter_transfer_def
by (simp add: sp_bind_def sp_return_def ac_simps)
lemma enter_deps_keyed_enter_transfer:
"enter_deps (keyed_enter_transfer cmb rl rg free R W en) (mk_dg_man d key) σ
(map (λ(c, e). (rl c, rl e)) (en (cmb d (view_of rg R (genv key σ) (free R)))))
((Inr ∘ key) ` set R)"
unfolding enter_deps_def keyed_enter_transfer_def
by (simp add: sp_bind_def sp_return_def)
text ‹Only the manager's local value and its two global capabilities are read, so
the query channel installed around a transfer does not change it.›
lemma read_view_outer_man [simp]:
"read_view (outer_man Q m) rg xs acc = read_view m rg xs acc"
by (induction xs arbitrary: acc) simp_all
lemma publish_at_outer_man [simp]:
"publish_at (outer_man Q m) rg xs r = publish_at m rg xs r"
by (induction xs) simp_all
lemma keyed_transfer_outer_man [simp]:
"keyed_transfer cmb rl rg free R W f (outer_man Q m) = keyed_transfer cmb rl rg free R W f m"
by (simp add: keyed_transfer_def)
subsection ‹The lifter›
text ‹
Every edge runs the component's closed step through ‹keyed_transfer›, reading
the globals its expressions mention and publishing the globals it may assign.
A return reads nothing and may assign its destination. The return pipeline is
wrapped once, as in the ownership split.
A call reads the globals its actuals mention and passes every other global as
‹free›. The context a call enters is computed from that state, and the analyzer
recomputes it outside the solver from the same view of the solved environment.
›
definition keyed_split_spec ::
"(vname ⇒ bool) ⇒ ('d ⇒ 'd ⇒ 'd) ⇒ ('d ⇒ 'd) ⇒ (vname ⇒ 'd ⇒ 'd)
⇒ (vname list ⇒ 'd) ⇒ 'd local_spec
⇒ ('x,'k,vname,'d::bounded_semilattice_sup_bot,'d) dg_spec"
where
"keyed_split_spec 𝒢 cmb rl rg free c =
(let step = (λa. keyed_transfer cmb rl rg free (edge_global_reads 𝒢 a)
(edge_global_writes 𝒢 a) ⟦a⟧⇧♯⇘c⇙)
in local_dg_spec_template⦇
dgs_skip := step EA_Nop,
dgs_assign := (λx e. step (EA_Assign x e)),
dgs_special := (λsc x. step (EA_Special sc x)),
dgs_branch := (λb pol. step (if pol then EA_Assume b else EA_AssumeNot b)),
dgs_body := (λp. step (EA_Body p)),
dgs_return := (λe p. step (EA_Ret e p)),
dgs_enter := (λci. keyed_enter_transfer cmb rl rg free
(call_global_reads 𝒢 (ci_args ci)) (global_names_in 𝒢 (ci_formals ci))
(λd. ls_enter c (ls_channel c d) ci (d, d))),
dgs_event := (λev. step (event_action ev)),
dgs_combine_assign := (λci. keyed_combine_transfer cmb rl rg free
(global_names_in 𝒢 (case_option [] (λx. [x]) (ci_dst ci)))
(λdc de. ls_combine c (ls_channel c dc) (ls_channel c de) ci dc de)) ⦈)"
lemma dg_spec_step_keyed_split_spec [simp]:
"dg_spec_step (keyed_split_spec 𝒢 cmb rl rg free c) a
= keyed_transfer cmb rl rg free (edge_global_reads 𝒢 a) (edge_global_writes 𝒢 a)
⟦a⟧⇧♯⇘c⇙"
unfolding keyed_split_spec_def Let_def by (cases a) simp_all
lemma dgs_enter_keyed_split_spec [simp]:
"enter⇧♯ (keyed_split_spec 𝒢 cmb rl rg free c) ci
= keyed_enter_transfer cmb rl rg free (call_global_reads 𝒢 (ci_args ci))
(global_names_in 𝒢 (ci_formals ci)) (λd. ls_enter c (ls_channel c d) ci (d, d))"
unfolding keyed_split_spec_def Let_def by simp
lemma dgs_query_keyed_split_spec [simp]:
"dgs_query (keyed_split_spec 𝒢 cmb rl rg free c) m q = sp_return ⊤"
unfolding keyed_split_spec_def Let_def by simp
lemma dg_spec_combine_transfer_keyed_split_spec [simp]:
"dg_spec_combine_transfer (keyed_split_spec 𝒢 cmb rl rg free c) ci
= keyed_combine_transfer cmb rl rg free (global_names_in 𝒢 (case_option [] (λx. [x]) (ci_dst ci)))
(λdc de. ls_combine c (ls_channel c dc) (ls_channel c de) ci dc de)"
unfolding dg_spec_combine_transfer_def keyed_split_spec_def Let_def
by (simp add: local_combine_transfer_def fun_eq_iff)
lemma dg_spec_wf_keyed_split_spec [intro, simp]:
"dg_spec_wf (keyed_split_spec 𝒢 cmb rl rg free c)"
unfolding dg_spec_wf_def
by (auto simp: keyed_transfer_def keyed_enter_transfer_def keyed_combine_transfer_def
intro!: sp_wf_bind)
subsection ‹Soundness of the keyed lifter›
text ‹
A point's state is its local half recombined with the global half its
environment describes over the program's global names ‹xs›. The carrier owes
monotone operations, and that a name outside a read set is cut to something
below ‹free› of that set. What it owes about concretization is one
frame law, ‹mix›: a store described by a result, whose globals outside
‹W› agree with a store described against an environment, is described by the
result's local half recombined with the result's own cut at the names in
‹W› and the environment at every other name.
›
theorem keyed_split_contract:
fixes cmb :: "'d::bounded_semilattice_sup_bot ⇒ 'd ⇒ 'd"
assumes sound: "sound_local_spec 𝒢 gm c"
and cmb_mono: "⋀d d' g g'. d ≤ d' ⟹ g ≤ g' ⟹ cmb d g ≤ cmb d' g'"
and rg_mono: "⋀x v v'. v ≤ v' ⟹ rg x v ≤ rg x v'"
and rg_free: "⋀x R v. x ∉ set R ⟹ rg x v ≤ free R"
and mix: "⋀l0 e r s s' W. s ∈ gm (cmb l0 (full_view rg xs e)) ⟹ s' ∈ gm r
⟹ (∀x. 𝒢 x ⟶ x ∉ set W ⟶ s' x = s x)
⟹ s' ∈ gm (cmb (rl r) (full_view rg xs (λx. if x ∈ set W then rg x r else e x)))"
shows "analysis_contract (keyed_split_spec 𝒢 cmb rl rg free c)
(λd e. gm (cmb d (full_view rg xs e))) 𝒢"
proof -
have gm_mono_all: "∀x y. x ≤ y ⟶ gm x ⊆ gm y"
using sound unfolding sound_local_spec_def by (rule conjunct1)
then have gm_mono: "⋀x y. x ≤ y ⟹ gm x ⊆ gm y" by blast
have env_mono: "⋀d d' e e'. d ≤ d' ⟹ (⋀x. e x ≤ e' x)
⟹ gm (cmb d (full_view rg xs e)) ⊆ gm (cmb d' (full_view rg xs e'))"
by (rule gm_mono, rule cmb_mono, assumption, rule full_view_mono[OF rg_mono]) simp
have view_ge: "full_view rg xs e ≤ view_of rg R e (free R)" for e R
unfolding full_view_def
proof (rule view_of_le)
fix x assume "x ∈ set xs"
show "rg x (e x) ≤ view_of rg R e (free R)"
proof (cases "x ∈ set R")
case True then show ?thesis by (rule view_of_ge_mem)
next
case False
then show ?thesis using rg_free view_of_ge_acc by (blast intro: order_trans)
qed
qed simp
have out: "(λx. if x ∈ set W then rg x r else e x) x ≤ (e ⊔ genv key P) x"
if pub: "⋀x. x ∈ set W ⟹ rg x r ≤ dg_global (P (Inr (key x)))"
for e W r x key and P :: "'z + 'w ⇒ ('d, 'd) dg_state"
using pub[of x] by (auto intro: le_supI2)
have pub: "rg x r ≤ dg_global ((bot ⊔ pub_sides key rg W r) (Inr (key x)))"
if "x ∈ set W" for x W r key
using pub_sides_ge[OF that, of rg r key] by (simp add: less_eq_dg_state_def)
show ?thesis
proof (unfold_locales, goal_cases wf mono step comb)
case wf show ?case by (rule dg_spec_wf_keyed_split_spec)
next
case (mono d d' e e')
then show ?case by (intro env_mono) (simp_all add: le_fun_def)
next
case (step a τ src key)
let ?d = "dg_local (τ src)" and ?e = "genv key τ"
let ?R = "edge_global_reads 𝒢 a" and ?W = "edge_global_writes 𝒢 a"
let ?g = "view_of rg ?R ?e (free ?R)"
let ?r = "⟦a⟧⇧♯⇘c⇙ (cmb ?d ?g)"
have loc: "edge_out (keyed_split_spec 𝒢 cmb rl rg free c) a src key τ = rl ?r"
by (simp add: dg_spec_edge_program_def traverse_transfer_program keyed_transfer_def
sp_compile_with_def sp_bind_def sp_return_def)
have sides: "sides_of_program
(dg_spec_edge_program (keyed_split_spec 𝒢 cmb rl rg free c) a src key) τ
= bot ⊔ pub_sides key rg ?W ?r"
by (simp add: dg_spec_edge_program_def sides_transfer_program keyed_transfer_def
sp_compile_with_def sp_bind_def sp_return_def bot_fun_def[symmetric])
show ?case
proof
fix s' assume "s' ∈ edge_collect a (gm (cmb ?d (full_view rg xs ?e)))"
then obtain s where s: "s ∈ gm (cmb ?d (full_view rg xs ?e))" and st: "s' ∈ edge_step a s"
by (auto simp: edge_collect_def)
have "s ∈ gm (cmb ?d ?g)"
using s gm_mono[OF cmb_mono[OF order_refl view_ge]] by blast
then have "s' ∈ edge_collect a (gm (cmb ?d ?g))" using st by (auto simp: edge_collect_def)
then have r: "s' ∈ gm ?r" using closed_step_sound[OF sound] by blast
have fr: "∀x. 𝒢 x ⟶ x ∉ set ?W ⟶ s' x = s x"
using edge_step_frame[OF st] by auto
have "s' ∈ gm (cmb (rl ?r) (full_view rg xs (λx. if x ∈ set ?W then rg x ?r else ?e x)))"
by (rule mix[OF s r fr])
also have "… ⊆ gm (cmb (rl ?r) (full_view rg xs (?e ⊔ genv key (bot ⊔ pub_sides key rg ?W ?r))))"
by (rule env_mono[OF order_refl], rule out, erule pub)
finally show "s' ∈ gm (cmb (edge_out (keyed_split_spec 𝒢 cmb rl rg free c) a src key τ)
(full_view rg xs
(genv key τ ⊔ edge_pub (keyed_split_spec 𝒢 cmb rl rg free c) a src key τ)))"
unfolding loc sides .
qed
next
case (comb s dc key τ t de ci)
let ?e = "genv key τ" and ?W = "global_names_in 𝒢 (case_option [] (λx. [x]) (ci_dst ci))"
let ?h = "λdc de. ls_combine c (ls_channel c dc) (ls_channel c de) ci dc de"
let ?r = "?h (cmb dc (free [])) (cmb de (free []))"
have free_ge: "full_view rg xs ?e ≤ free []"
unfolding full_view_def by (rule view_of_le) (simp_all add: rg_free)
have sc: "s ∈ gm (cmb dc (free []))"
using comb(1) gm_mono[OF cmb_mono[OF order_refl free_ge]] by blast
have tc: "t ∈ gm (cmb de (free []))"
using comb(2) gm_mono[OF cmb_mono[OF order_refl free_ge]] by blast
have r: "combine_collect 𝒢 (ci_dst ci) s t ∈ gm ?r"
by (rule closed_combine_sound[OF sound sc tc])
have fr: "∀x. 𝒢 x ⟶ x ∉ set ?W ⟶ combine_collect 𝒢 (ci_dst ci) s t x = t x"
by (auto intro: combine_collect_frame)
have "combine_collect 𝒢 (ci_dst ci) s t
∈ gm (cmb (rl ?r) (full_view rg xs (λx. if x ∈ set ?W then rg x ?r else ?e x)))"
by (rule mix[OF comb(2) r fr])
also have "… ⊆ gm (cmb (rl ?r) (full_view rg xs (?e ⊔ genv key (bot ⊔ pub_sides key rg ?W ?r))))"
by (rule env_mono[OF order_refl], rule out, erule pub)
finally show ?case
by (simp add: keyed_combine_transfer_def sp_compile_with_def sp_bind_def sp_return_def
bot_fun_def[symmetric])
qed
qed
end