Theory Nonrelational_State
theory Nonrelational_State
imports State_Concretization
begin
section ‹What a store of one abstract value per variable denotes, and when it denotes nothing›
text ‹
A non-relational abstract state, ‹'a abs_state›, pairs every program
variable with one abstract value, independently of every other variable
-- the "non-relational" in the name. ‹gamma_state› says what that
pairing denotes: exactly the concrete stores where each variable's value
lies in its own component's concretization, i.e. the product of the
per-component concretizations. Because that product is a conjunction over
every variable, one witness-bottom component -- one variable whose
abstract value denotes no concrete value at all -- already makes the
whole state denote nothing, regardless of what every other component
holds. ‹is_empty_state› names that witness-bottom condition directly,
so a generic transfer dispatcher can detect and canonicalize it without
testing ‹gamma_state› against the empty set at run time.
›
type_synonym 'a abs_state = "vname => 'a"
instance "fun" :: (type, bounded_semilattice_sup_bot) bounded_semilattice_sup_bot ..
text ‹Pointwise join on abstract states is idempotent because the value-domain
semilattice structure lifts pointwise. Finite folds can therefore use the
standard idempotent-join laws without a separate state-level assumption.›
subsection ‹State concretization›
definition gamma_state :: "('a::numeric_domain) abs_state ⇒ store set" where
"gamma_state d = {s. ∀x. s x ∈ γ (d x)}"
adhoc_overloading gamma_S == gamma_state
lemma gamma_stateI [intro]:
"(⋀x. s x ∈ γ (d x)) ⟹ s ∈ γ d"
unfolding gamma_state_def by simp
subsection ‹State concretization laws›
text ‹Every value-level concretization law, lifted to states. All of them fall out
pointwise, which is exactly what makes a nonrelational state cheap: order, bottom and join
are read one variable at a time, and no law here needs a relation between variables.›
lemma gamma_state_mono:
"sigma1 ≤ sigma2 ⟹ γ sigma1 ⊆ γ sigma2"
for sigma1 sigma2 :: "'a::numeric_domain abs_state"
unfolding gamma_state_def le_fun_def
using gamma_mono by blast
lemma gamma_state_bot [simp]:
"γ (bot :: 'a::numeric_domain abs_state) = {}"
unfolding gamma_state_def bot_fun_def using gamma_bot by auto
lemma gamma_state_sup_ub1 [intro]:
"γ sigma1 ⊆ γ (sigma1 ⊔ sigma2)"
for sigma1 sigma2 :: "'a::numeric_domain abs_state"
unfolding gamma_state_def sup_fun_def
using gamma_sup_ub1 by blast
lemma gamma_state_sup_ub2 [intro]:
"γ sigma2 ⊆ γ (sigma1 ⊔ sigma2)"
for sigma1 sigma2 :: "'a::numeric_domain abs_state"
unfolding gamma_state_def sup_fun_def
using gamma_sup_ub2 by blast
text ‹
Membership counterparts of ‹gamma_state_sup_ub1›/‹gamma_state_sup_ub2›,
for citing at a witness ‹s› directly (‹using subsetD[OF gamma_state_sup_ub1 h]›
otherwise repeats the same ‹subsetD› wrapper at every call site).
‹[intro]›: their conclusion has the distinctive
‹s ∈ γ (sigma1 ⊔ sigma2)› shape a join-injection goal actually has, and a
witness already in one side's concretization is exactly the natural way to
discharge it.
›
lemma gamma_state_supI1 [intro]:
"s ∈ γ sigma1 ⟹ s ∈ γ (sigma1 ⊔ sigma2)"
for sigma1 sigma2 :: "'a::numeric_domain abs_state"
using gamma_state_sup_ub1 by blast
lemma gamma_state_supI2 [intro]:
"s ∈ γ sigma2 ⟹ s ∈ γ (sigma1 ⊔ sigma2)"
for sigma1 sigma2 :: "'a::numeric_domain abs_state"
using gamma_state_sup_ub2 by blast
text ‹
Left bare rather than ‹[dest]›: ‹x› is unconstrained by the premise, so a
global destruction rule here would let ‹auto› pick a schematic variable
unrelated to the goal at hand (as with the ‹Voblint_Solver› session's
‹env_indep_depsD›). Cite it explicitly with the variable the goal needs.
›
lemma gamma_stateD:
"s ∈ γ d ⟹ s x ∈ γ (d x)"
for d :: "'a::numeric_domain abs_state"
unfolding gamma_state_def by simp
subsection ‹Witness-bottom abstract states›
text ‹
Witness-bottom is not "the state has exactly one empty component": any
number of a state's components can independently denote the empty set of
concrete integers at once, and ‹is_empty_state› does not distinguish those
cases from each other or count them. It only asks whether at least one
witness exists (‹∃x. is_empty (d x)›), because one witness already
suffices -- ‹gamma_state›'s product structure means a single empty
component collapses the whole state's concretization to the empty set of
stores, so a second, third, or every remaining empty component would only
reconfirm what the first already decided.
‹is_empty_state› is a logical characterization of that condition, not an
executable test: ‹'a abs_state› is a raw function ‹vname ⇒ 'a›, and
\<^typ>‹vname› is infinite, so ‹∃x. is_empty (d x)› has no code equation --
there is no finite witness search for code generation to compile. The
finite executable bottom test a real dispatcher runs is ‹default_st_is_bot_for›,
defined downstream in the ‹Voblint_Exec› session, which scans only the
state's finitely many stored locations and is proved to agree with
‹is_empty_state› on every such state.
›
definition is_empty_state :: "('a::executable_domain) abs_state ⇒ bool" where
"is_empty_state d = (∃x. is_empty (d x))"
lemma is_empty_stateI [intro]:
"is_empty (d x) ⟹ is_empty_state d"
unfolding is_empty_state_def by (rule exI)
lemma is_empty_stateE [elim]:
assumes "is_empty_state d"
obtains x where "is_empty (d x)"
using assms unfolding is_empty_state_def by blast
text ‹
Overwriting one variable of a state that is not already empty can only make
it empty through the written element. This is what lets an incremental
emptiness check inspect the freshly computed element alone instead of
searching the infinite variable space again.
›
lemma is_empty_state_fun_upd_iff:
assumes "¬ is_empty_state d"
shows "is_empty_state (d(x := a)) ⟷ is_empty a"
proof
assume "is_empty_state (d(x := a))"
then obtain y where "is_empty ((d(x := a)) y)"
by (rule is_empty_stateE)
with assms show "is_empty a"
by (cases "y = x") auto
next
assume "is_empty a"
then have "is_empty ((d(x := a)) x)" by simp
then show "is_empty_state (d(x := a))"
by (rule is_empty_stateI)
qed
lemma is_empty_state_gamma_state_empty:
assumes "is_empty_state d"
shows "γ d = {}"
proof -
obtain x where "is_empty (d x)" using assms by (rule is_empty_stateE)
then show ?thesis
using gamma_stateD[of _ d x] unfolding is_empty_correct by fastforce
qed
lemma gamma_state_empty_is_empty_state:
assumes "γ d = {}"
shows "is_empty_state d"
proof (rule ccontr)
assume "¬ is_empty_state d"
then have nonempty: "⋀x. ∃v. v ∈ γ (d x)"
unfolding is_empty_state_def using is_empty_correct by blast
define s where "s = (λx. SOME v. v ∈ γ (d x))"
have s_prop: "s x ∈ γ (d x)" for x
unfolding s_def using nonempty[of x] by (rule someI_ex)
then have "s ∈ γ d"
unfolding gamma_state_def by simp
with assms show False by simp
qed
lemma is_empty_state_iff_gamma_state_empty:
"is_empty_state d ⟷ γ d = {}"
using is_empty_state_gamma_state_empty gamma_state_empty_is_empty_state by blast
lemma exact_emptiness_is_empty_state:
"exact_emptiness is_empty_state (gamma_state :: 'a::numeric_domain abs_state ⇒ store set)"
by (rule exact_emptinessI) (rule is_empty_state_iff_gamma_state_empty)
lemma is_empty_state_bot [simp]:
"is_empty_state (bot :: 'a::numeric_domain abs_state)"
unfolding is_empty_state_def bot_fun_def
using is_empty_correct gamma_bot by blast
lemma is_empty_state_antimono:
"d1 ≤ d2 ⟹ is_empty_state d2 ⟹ is_empty_state d1"
for d1 d2 :: "'a::numeric_domain abs_state"
unfolding is_empty_state_def le_fun_def using is_empty_antimono by blast
end