* Rules that didn't make it
** A binding rule for the semantic judgment that /nearly/ works.
A rule that we would like to have
#+BEGIN_SRC
WP e {{ v, {E,E;Γ} ⊧ K[v] ≤ e₂ : τ }}
-----------------------------------------
{E,E;Γ} ⊧ K[e₁] ≤ e₂ : τ
#+END_SRC
However, this rule is too weak. Consider a derivation where =e₁= is
atomic and we want to open an invariant to symbolically execute it.
#+BEGIN_SRC
|={E1,E2}=> WP e {{ v, |={E2,E1}=> {E1,E1;Γ} ⊧ K[v] ≤ e₂ : τ }}
---------------------------------------------------------------- wp-atomic
WP e {{ v, {E1,E1;Γ} ⊧ K[v] ≤ e₂ : τ }}_E1
-----------------------------------------
{E1,E1;Γ} ⊧ K[e₁] ≤ e₂ : τ
#+END_SRC
After employing such reasoning we can open up an invariant, and
symbolically execute =e₁=. But then we are left with
#+BEGIN_SRC
|={E2,E1}=> {E1,E1;Γ} ⊧ K[v] ≤ e₂ : τ
#+END_SRC
which means that we have to close the invariant *now*. However, it
might be the case that we need to symbolically execute =e₂= in order
to restore the invariant before closing it.
* Assorted
** Logically atomic rules for the relatoinal interpretation
<2017-07-10 ma>
Consider the following rule for the left hand side for some function `inc`:
#+BEGIN_SRC
x ↦ᵢ n+1 ⊢ {E,E;Γ} ⊧ K[()] ≤ t : τ
----------------------------------------
x ↦ᵢ n ⊢ {E,E;Γ} ⊧ K[inc #x ()] ≤ t : τ
#+END_SRC
For a definition of =inc= in F_mu_ref_conc we can prove this rule
using basic inductive rules for the left hand side. However, we may
encounter the same issue as with Hoare triples: this rule is unusable
in situations when `x ↦ n` is in the invariant. The program =inc= is
not actually atomic, so we cannot open the invariant around it.
However, it is /logically/ atomic, i.e. it behaves "as if" it is physically
atomic.
We may want to write the aforementioned rule in a different style:
#+BEGIN_SRC
Lemma bin_log_FG_increment_logatomic R Γ E1 E2 K x t τ :
□ (|={E1,E2}=> ∃ n, x ↦ᵢ (#nv n) ∗ R(n) ∗
((∃ n, x ↦ᵢ (#nv n) ∗ R(n)) ={E2,E1}=∗ True) ∧
(∀ m, x ↦ᵢ (#nv (S m)) ∗ R(m) -∗
{E2,E1;Γ} ⊨ fill K (Lit Unit) ≤log≤ t : τ))
-∗ ({E1,E1;Γ} ⊨ fill K (FG_increment (Loc x) Unit) ≤log≤ t : τ).
#+END_SRC
To use this rule, we need to provide, possibly under an invariant (by
opening invariants in E1)
- a way of obtaining =x ↦ᵢ n= and a "frame" =R(n)=;
- a way of "reversing" the rule in case of failure;
- a way of "finishing up" the rule in case of success;
where the two last points do not have to be provided simultaneously.
*** Some references on logical atomicity
- Triples for logical atomicity have originally been introduced in the following paper:
TaDA: A Logic for Time and Data Abstraction
Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner
ECOOP'2014
http://www.doc.ic.ac.uk/~pmd09/research/publications/2014/ecoop/tada-a-logic-for-time-and-data-abstraction
- In the original Iris 1.0 paper, it has been shown how atomic triples can be encoded in Iris (using view shifts, and higher-order quantification).
- Zhen Zhang, a former intern of Derek Dreyer, has made some progress at formalizing logical atomicity in Coq. His results can be found here:
https://gitlab.mpi-sws.org/FP/iris-atomic
He improved the definition of the logical atomic triple somewhat.
The relevant files to look at are: atomic.v, the definition of
logical atomic triples, atomic_incr.v, a proof for a simple atomic
incrementer, and treiber.v for Treiber stack.
** Why do we need the expressions to be closed everywhere?
<2017-07-10 ma>
(For many lemmas in [[file:F_mu_ref_conc/relational_properties.v]]
This is not a very serious restriction, as the "real" programs are always closed,
and you shouldn't attempt symbolic execution of open expressions.
For =wp_lift_pure_det_head_step_no_fork= you have to prove that your
expression is not a value -- this is no longer true if you have open
expressions. If you have =x=, then closing it up by an env
substitution will yield a value.
** Why do we have a single =bin_log_related_cas_l=?
=bin_log_related_cas_l= is a rule in [[file:F_mu_ref_conc/relational_properties.v]]
We already have =bin_log_related_cas_suc_l= and
=bin_log_related_cas_fail_l=. However, those rules are not sufficient
for our purposes, as we might not know whether the CAS succeeds
/before/ we open the invariant.
Example: proof of =bin_log_FG_increment_logatomic= in counter.v
** subst_p_rec with binders
#+BEGIN_SRC
Lemma subst_p_rec (x f : binder) e1 e2 e `{Closed ∅ e2} :
subst_p (<[x:=e2]>{[f:=(Rec f x e1)]}) e =
subst' f (Rec f x e1) (subst' x e2 e).
#+END_SRC
We need a version of this lemma with x and f binders
in order to prove the compatibility lemma for lambdas
#+BEGIN_SRC
□ (<[x:=τ1]>(<[f:=TArrow τ1 τ2]>Γ) ⊨ e ≤log≤ e' : τ2) -∗
Γ ⊨ Rec f x e ≤log≤ Rec f x e' : TArrow τ1 τ2.
#+END_SRC
To show tht (rec f x e, rec f x e') are related, we use the definition
of the arrow type, so we have some values (v, v') related at τ1. Then
we would like to use the hypothesis by inserting (v, v') in the
environment substitution. But then we have =subst_p (<[x:=(v,v')]>
...) e=. You would like to convert that to =subst' x ..= so that one
can actually use the hypothesis.
* Comments on the design
** Common setting for different symbolic execution tactics
There are multiple layers of tactics for symbolic execution in the code.
*** Layers of tactics
We have symbolic execution rules (either as lemmas or as tactics and tactic lemmas) for
- WP calculus
- Symbolic execution in the ghost threadpool
- Symbolic execution in the relational interpretation
*** Tactics and tactic lemmas
Here we make a distinction between an actual /tactic/ and a /tacic lemma/.
A /tactic lemma/ is a lemma in Coq that is a Coq proposition that is saying more than a certain ~iProp~ holds.
Hence, the tacic lemma is typechecked. A /tactic/ is an actual piece of Ltac code that makes it more convenient to apply the aforementioned lemma.
For instance, this is a tactic lemma for symbolically executing a ~Load~ in the ghost threadpool.
#+BEGIN_SRC coq
Lemma tac_tp_load `{logrelG Σ} j Δ1 Δ2 Δ3 E1 E2 ρ i1 i2 i3 p K' e l v Q q :
nclose specN ⊆ E1 →
envs_lookup i1 Δ1 = Some (p, spec_ctx ρ) →
envs_lookup_delete i2 Δ1 = Some (false, j ⤇ e, Δ2)%I →
e = fill K' (Load (Loc l)) →
envs_lookup i3 Δ2 = Some (false, l ↦ₛ{q} v)%I →
envs_simple_replace i3 false
(Esnoc (Esnoc Enil i2 (j ⤇ fill K' (of_val v))) i3 (l ↦ₛ{q} v)%I) Δ2 = Some Δ3 →
(Δ3 ⊢ |={E1,E2}=> Q) →
(Δ1 ⊢ |={E1,E2}=> Q).
#+END_SRC
It takes care of managing the context in a typesafe way.
Then this lemma is actually applied with the help of the following tactic
#+BEGIN_SRC coq
Tactic Notation "tp_load" constr(j) :=
iStartProof;
eapply (tac_tp_load j);
[solve_ndisj || fail "tp_load: cannot prove 'nclose specN ⊆ ?'"
|iAssumptionCore || fail "tp_load: cannot find spec_ctx" (* spec_ctx *)
|iAssumptionCore || fail "tp_load: cannot find '" j " ⤇ ?'"
|tp_bind_helper
|iAssumptionCore || fail "tp_load: cannot find '? ↦ₛ ?'"
|env_cbv; reflexivity || fail "tp_load: this should not happen"
|(* new goal *)].
#+END_SRC
The tactic itself provides error messages as well as some means of discharging the assumptions for the tactic lemma.
Since the tactic lemma is being ~eapply~ed, it is always important to think about the order we put the assumptions in the lemma.
At the moment we apply the lemma Coq's unification mechanism instantiates ~Δ1~, ~E1~, ~E2~, and ~Q~.
We need to make sure that by discharging the assumptions in the correct order we can instantiate ~Δ3~ to something meaningful.
For instance, the fourth assumption says something about the shape of the expression that is executed in thread ~j~; however, this expression is not known until we lookup it up in the context.
Hence, the fourth assumption should come *after* the third one.
Likewise, the fifth assumption says that there is a resource ~l ↦ₛ v~ in context ~Δ2~, but we only know the location ~l~ once we decompose the expression ~e~ into an evaluation context and a load expression.
Thus, this assumption should come after ~e = fill K'(Load (Loc l))~.
*** Pure reductions
Since ultimately all the levels of the symbolic executions operate on the same language we can share some machinery between the levels.
One observation is that the easiest (as well as the most numerous) tactics to write are those that preform symbolic execution of pure reductions.
To that extent we would like to have one generic "pure symbolic reduction" tactic/tactic lemma per level.
This is achieved by writing "open ended" tactic lemmas that are parameterized over a typeclass of pure reductions.
The typeclass ~PureExec φ e1 e2~ says that there is a pure (and deterministic) reduction from ~e1~ to ~e2~ in the language under some (pure) assumption φ.
Using this typeclass and typeclass resolution we can write tactic lemmas that are easy to apply:
#+BEGIN_SRC
Lemma tac_rel_pure_r `{logrelG Σ} K e1 ℶ E1 E2 Δ Γ e e2 eres ϕ t τ :
e = fill K e1 →
PureExec ϕ e1 e2 →
ϕ →
nclose specN ⊆ E1 →
eres = fill K e2 →
(ℶ ⊢ {E1,E2;Δ;Γ} t ≤log≤ (fill K e2) : τ) →
(ℶ ⊢ {E1,E2;Δ;Γ} t ≤log≤ e : τ).
#+END_SRC
When ~tac_rel_pure~ is applied, it tries to solve the first goal ~e = fill K e1~ by decomposing expression ~e~ in all the possible ways
and trying to unify it with ~e1~. After that it searches for an appropriate ~PureExec~ instance.
The whole process is implemented in the following tactic
#+BEGIN_SRC
Tactic Notation "rel_pure_r" open_constr(ef) :=
iStartProof;
rel_reshape_cont_r ltac:(fun K e' =>
unify e' ef;
simple eapply (tac_rel_pure_r K e');
[tac_bind_helper (* e = fill K e1 *)
|apply _ (* PureExec ϕ e1 e2 *)
|try (exact I || reflexivity || tac_rel_done) (* φ *)
|solve_ndisj (* ↑specN ⊆ E1 *)
|simpl; reflexivity (* eres = fill K e2 *)
|simpl_subst/= (* new goal *)])
|| fail "rel_pure_r: cannot find the reduct".
#+END_SRC
Ignoring the "fail" part, we can see from the outline what is going on in the tactic:
- It tries to reshape the expression on the right hand side in the relational interpretation, until the tactic succeeds.
- It only proceeds with the specific decomposition if ~e = K[e']~ and ~e'~ unifies with ~ef~.
- After that, it applies the tactic lemma and tries to discharge the proof obligations in order
- Sometimes, the dependency ~φ~ in ~PureExec φ e1 e2~ can be discharged trivially by ~reflexivity~ or ~tac_rel_done~.
- We simplify the substitutions in the new goal, in case we do a beta-reduction