From c0e72f313a36b70d4e50151430ab5913141946b2 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 20 Aug 2026 20:32:09 +0200 Subject: [PATCH 001/109] WIP on thread_id proofs --- rocq-brick-libstdcpp/proof/thread/pred.v | 6 ++++++ rocq-brick-libstdcpp/proof/thread/spec.v | 0 2 files changed, 6 insertions(+) create mode 100644 rocq-brick-libstdcpp/proof/thread/pred.v create mode 100644 rocq-brick-libstdcpp/proof/thread/spec.v diff --git a/rocq-brick-libstdcpp/proof/thread/pred.v b/rocq-brick-libstdcpp/proof/thread/pred.v new file mode 100644 index 00000000..8f05d106 --- /dev/null +++ b/rocq-brick-libstdcpp/proof/thread/pred.v @@ -0,0 +1,6 @@ +Require Import skylabs.auto.cpp.prelude.pred. +Require Import skylabs.brick.libstdcpp.runtime.pred. + +Section with_cpp. + Context `{Σ : cpp_logic}. +End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v new file mode 100644 index 00000000..e69de29b From 4f2111bcc58db04a82ee6d88db6ac3a916b6b15e Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 31 Aug 2026 18:13:50 +0000 Subject: [PATCH 002/109] [mutex.v]: change token to user; comment out with_RepFor section --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index f013828f..c2254964 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -12,8 +12,10 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Import linearity. +Import lock_ghost. (* TODO UPSTREAM. *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. @@ -28,21 +30,14 @@ Section with_cpp. #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). - (** Owning [mutex_token γ 1] proves that the mutex is not locked, and - therefore can be safely destroyed: the standard specifies that calling - [std::mutex::~mutex()] while holding the lock results in undefined behavior. - *) - Parameter token : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> Qp -> mpred. - #[only(fractional,fracvalid,asfractional,timeless)] derive token. - - Section with_RepFor. + (* Section with_RepFor. Import rep.RepFor. Import RepScheme. #[global] Instance repfor `{!HasStdThreads Σ} {σ : genv} : rep.RepFor.C "std::mutex" [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] - (funI γ q P => R γ q P ∗ pureR (token γ q)) := {}. - End with_RepFor. + (funI γ q P => R γ q P) := {}. + End with_RepFor. *) (** A resource enforcing that the thread calling unlock must be the same thread @@ -65,27 +60,36 @@ Section with_cpp. >> *) Parameter locked : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - gname -> thread_idT -> Qp -> mpred. + gname -> thread_idT -> mpred. #[only(timeless)] derive locked. - (** locked takes a [Qp] but _cannot_ be split. *) #[only(exclusive)] derive locked. + Context `{!lockG Σ}. + + Abbreviation used_threads γ s := + (lock_ghost.used_threads γ s). + + Abbreviation users γ ths := + (lock_ghost.users γ ths). + + Abbreviation user γ th := (users γ {[ th ]}). + Context `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Instance locked_learn : Cbn (Learn (req_eq ==> learn_eq ==> learn_eq ==> learn_hints.fin) locked). + #[global] Instance locked_learn : Cbn (Learn (req_eq ==> learn_eq ==> learn_hints.fin) locked). Proof. solve_learnable. Qed. cpp.spec "std::mutex::mutex()" as ctor_spec with ( \this this \pre{P} ▷P - \post Exists g, this |-> R g 1$m P ** token g 1). + \post Exists g, this |-> R g 1$m P ** used_threads g ∅). cpp.spec "std::mutex::~mutex()" as dtor_spec with ( \this this - \pre{g P} this |-> R g 1$m P ** token g 1 + \pre{g P} this |-> R g 1$m P ** used_threads g ∅ \post P). (* "Inline" version of these specs. *) @@ -93,41 +97,41 @@ Section with_cpp. \this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{q'} token g q' - \post P ** locked g thr q'). + \pre user g thr + \post P ** locked g thr). Definition do_lock (lk : gname * mpred) (K: mpred) : mpred := let g := lk.1 in let P := lk.2 in - ∃ q thr, current_thread thr ∗ token g q ∗ + ∃ thr, current_thread thr ∗ user g thr ∗ (* TODO readd *) (* ▷ *) - (locked g thr q ** P -* K). + (locked g thr ** P -* K). #[global] Arguments do_lock /. cpp.spec "std::mutex::unlock()" as unlock_spec_alt with ( \this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{q'} locked g thr q' + \pre locked g thr \pre ▷P - \post token g q'). + \post user g thr). Definition do_unlock (lk : gname * mpred) (Q : mpred) : mpred := let g := lk.1 in let P := lk.2 in - Exists q thr, current_thread thr ** locked g thr q ** ▷P ** + Exists thr, current_thread thr ** locked g thr ** ▷P ** (* TODO readd *) (* ▷ *) - (token g q -* Q). + (user g thr -* Q). #[global] Arguments do_unlock /. cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with ( \this this \prepost{q P g} this |-> R g q P \persist{th} current_thread th - \pre{q'} token g q' - \post{b}[Vbool b] if b then P ** locked g th q' else token g q'). + \pre user g th + \post{b}[Vbool b] if b then P ** locked g th else user g th). (* Obtain same specs from (Basic)Lockable. *) (** <> implements [BasicLockable] *) @@ -146,9 +150,9 @@ Section with_cpp. Definition do_try_lock (lk : gname * mpred) (Q : bool -> mpred) : mpred := let g := lk.1 in let P := lk.2 in - ∃ q thr, current_thread thr ∗ token g q ∗ + ∃ thr, current_thread thr ∗ user g thr ∗ ∀ b : bool, - (if b then P ** locked g thr q else token g q) -∗ Q b. + (if b then P ** locked g thr else user g thr) -∗ Q b. #[global] Arguments do_try_lock /. #[global,program] Instance mutex_lockable : Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := @@ -173,4 +177,3 @@ Section with_cpp. Qed. End with_cpp. End mutex. - From 9c3b48c6544df6eb712e9fec07f2a7cf04aa3893 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 31 Aug 2026 18:40:05 +0000 Subject: [PATCH 003/109] replace appearance of mutex.token to user --- .../proof/mutex/proof/lock_guard.v | 5 ++--- .../proof/mutex/proof/unique_lock.v | 5 +++-- .../proof/mutex/spec/lock_guard.v | 11 +++++----- .../proof/mutex/spec/scoped_lock.v | 20 ++++++++++--------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index 74de4d0d..a828faae 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -3,12 +3,14 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.mutex.spec.lock_guard. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Import linearity. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!lock_ghost.lockG Σ}. Import lock_guard. @@ -17,9 +19,6 @@ Section with_cpp. Cbn (Learn (learn_eq ==> learn_eq ==> learn_eq ==> fin_at) mutex.R). Proof. solve_learnable. Qed. - #[global] Instance UNSAFE_token_learn : Cbn (Learn (req_eq ==> learn_eq ==> learn_hints.fin) mutex.token). - Proof. solve_learnable. Qed. - #[local] Hint Resolve fractional.UNSAFE_read_prim_learn : sl_opacity. Lemma ctor_ok : verify[source] ctor_spec. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v index 45ff3244..dc8a20d7 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v @@ -6,11 +6,13 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.prelude. Require Import skylabs.brick.libstdcpp.mutex.spec.unique_lock. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.lib.tactics. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. NES.Begin unique_lock. Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. + Context `{!lock_ghost.lockG Σ}. Import R_unfold. @@ -43,10 +45,9 @@ NES.Begin unique_lock. "std::unique_lock::unique_lock(std::mutex&)". Proof. verify_spec; go. - iExists K. + iExists K, q. (* Time Succeed solve [setoid_rewrite cQp.scale_mut; setoid_rewrite (right_id_L 1%Qp Qp.mul); ego with br_erefl]. *) - iExists _, q. rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). go with br_erefl. Qed. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 392b5445..c95924eb 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -3,6 +3,7 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Import linearity. @@ -47,6 +48,7 @@ Module lock_guard. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!lock_ghost.lockG Σ}. #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) lock_guard.R) := @@ -87,20 +89,20 @@ Section with_cpp. \arg{mp} "m" (Vptr mp) \persist{thr} current_thread thr \pre{g q P} mp |-> mutex.R g q$m P - \pre mutex.token g q + \pre lock_ghost.user g thr \post this |-> R (mp, g, q) 1$m P ** - P ** mutex.locked g thr q + P ** mutex.locked g thr ). cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( \this this \pre{mp g q P} this |-> R (mp, g, q) 1$m P \persist{thr} current_thread thr - \pre mutex.locked g thr q + \pre mutex.locked g thr \pre ▷P \post - mutex.token g q ** + lock_ghost.user g thr ** mp |-> mutex.R g q$m P ). @@ -124,4 +126,3 @@ Section with_cpp. End with_cpp. End lock_guard. - diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index 7a734e9f..b8811f8e 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -3,6 +3,7 @@ Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Module scoped_lock. Section with_cpp. @@ -17,6 +18,7 @@ Module scoped_lock. Section with_threads. Context {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. + Context `{!lock_ghost.lockG Σ}. #[global] Instance: LearnEqF1 R := ltac:(solve_learnable). @@ -25,17 +27,17 @@ Module scoped_lock. (* "std::scoped_lock::scoped_lock(std::mutex&, std::mutex&)" *) as ctor_spec from source with ( \this this + \persist{thr} current_thread thr \arg{mp1} "" (Vptr mp1) \pre{g1 q1 P1} mp1 |-> mutex.R g1 q1$m P1 - \pre mutex.token g1 q1 + \pre lock_ghost.user g1 thr \arg{mp2} "" (Vptr mp2) \pre{g2 q2 P2} mp2 |-> mutex.R g2 q2$m P2 - \pre mutex.token g2 q2 - \persist{thr} current_thread thr + \pre lock_ghost.user g2 thr \post this |-> R 1$m [ (mp1, g1, q1, P1); (mp2, g2, q2, P2)] ** - P1 ** mutex.locked g1 thr q1 ** - P2 ** mutex.locked g2 thr q2 + P1 ** mutex.locked g1 thr ** + P2 ** mutex.locked g2 thr ). cpp.spec "std::scoped_lock<...>::~scoped_lock()" @@ -50,11 +52,11 @@ Module scoped_lock. this |-> R 1$m [ (mp1, g1, q1, P1); (mp2, g2, q2, P2)] \pre |> P1 \pre |> P2 - \pre mutex.locked g1 thr q1 - \pre mutex.locked g2 thr q2 + \pre mutex.locked g1 thr + \pre mutex.locked g2 thr \post - mp1 |-> mutex.R g1 q1$m P1 ** mutex.token g1 q1 ** - mp2 |-> mutex.R g2 q2$m P2 ** mutex.token g2 q2 + mp1 |-> mutex.R g1 q1$m P1 ** lock_ghost.user g1 thr ** + mp2 |-> mutex.R g2 q2$m P2 ** lock_ghost.user g2 thr ). End with_threads. End with_cpp. From 39c9682680ef36e12f3a6bcabd39f45711a63832 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 31 Aug 2026 23:36:44 +0000 Subject: [PATCH 004/109] check in layout of a custom mutex proof --- .../test/mutex/custom_mutex_hpp_proof.v | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v new file mode 100644 index 00000000..70f0b4b1 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -0,0 +1,79 @@ +(** Provisional *) + +Require Import skylabs.auto.cpp.proof. +Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Import lock_ghost. +Require Import skylabs.brick.libstdcpp.atomic.spec. + +Module custom_mutex. + + Abbreviation N := "MyMutex"%cpp_name. + + + Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> option thread_idT -> Rep. + + Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. + Parameter owner_token_auth : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. + Parameter owner_token_frac : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. + + Record gname : Set := MkGname + { user_gname : iprop.gname + ; cinv_gname : iprop.gname + ; phys_state_gname : iprop.gname + }. + + Definition lock_namespace : namespace := nroot .@@ "MyMutex". + + Definition locked `{Σ : cpp_logic} `{!lockG Σ} (g: gname) (th : thread_idT) : mpred + := owner_token_auth g.(phys_state_gname) (Some th) ** user g.(user_gname) th. + + (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := + ∃ x, recursive_mutex.owned_count_id_auth γ.(rec_gname) x. *) +(* + Definition rawR `{Σ : cpp_logic, σ : genv} (owner : option thread_idT) (count : nat) : Rep := + structR "std::recursive_mutex" 1$m ** + _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) + + Section with_Σ. + Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !lockG Σ}. + + Definition mutex_content (γ : gname) : Rep := + ∃ o_owner lockedb, + _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m lockedb ** + _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m o_owner. + + Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := + ∃ o_owner, this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m o_owner ** + owner_token_frac γ.(phys_state_gname) o_owner ** + ∃ b : bool, + this ,, _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m b ** + if b then + ∃ owner, [| Some owner = o_owner |] + else + owner_token_auth γ.(phys_state_gname) o_owner ** + P. + + Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := + structR N q$m ** + as_Rep (fun this => + cinv lock_namespace γ.(cinv_gname) (mutex_inv this γ P) ** + cinv_own γ.(cinv_gname) q + ). + + Section proof. + + Context `{MOD : source ⊧ σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + + cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( + \this this + \pre{P} ▷P + \post Exists g, this |-> IR g 1$m P ** used_threads g.(user_gname) ∅). + + End proof. + + End with_Σ. + +End custom_mutex. From 07895212437a8e32e0865e2b538b49d2b083190b Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 1 Sep 2026 04:46:57 +0000 Subject: [PATCH 005/109] [custom_mutex_hpp_proof.v] add do_lock lemma statement --- .../test/mutex/custom_mutex_hpp_proof.v | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 70f0b4b1..2750bcfc 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -12,7 +12,9 @@ Module custom_mutex. Abbreviation N := "MyMutex"%cpp_name. - Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> option thread_idT -> Rep. + Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> + (* None if value is thread::id(), Some otherwise *) + option thread_idT -> Rep. Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. Parameter owner_token_auth : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. @@ -45,15 +47,21 @@ Module custom_mutex. _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m o_owner. Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := - ∃ o_owner, this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m o_owner ** + ∃ o_owner, owner_token_frac γ.(phys_state_gname) o_owner ** ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m b ** if b then - ∃ owner, [| Some owner = o_owner |] + emp else owner_token_auth γ.(phys_state_gname) o_owner ** - P. + P ** + (** m_owner does not concern do_lock() and do_unlock(), the actual + implementation of mutex, and does not always equal o_owner. + It is just a resource that one can get from the invariant. *) + ∃ m_owner : option thread_idT, + this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m m_owner + . Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR N q$m ** @@ -62,18 +70,42 @@ Module custom_mutex. cinv_own γ.(cinv_gname) q ). - Section proof. - Context `{MOD : source ⊧ σ}. - Context {HAS_THREADS : HasStdThreads Σ}. - cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( - \this this - \pre{P} ▷P - \post Exists g, this |-> IR g 1$m P ** used_threads g.(user_gname) ∅). - - End proof. + Context `{MOD : source ⊧ σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + + cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( + \this this + \pre{P} ▷P + \post Exists g, this |-> IR g 1$m P ** used_threads g.(user_gname) ∅). + + cpp.spec "MyMutex::~MyMutex()" as dtor_spec with ( + \this this + \pre{g P} this |-> IR g 1$m P ** used_threads g.(user_gname) ∅ + \post P). + + cpp.spec "MyMutex::do_lock()" as lock_spec with ( + \this this + \prepost{q P g} this |-> IR g q P + \persist{thr} current_thread thr + \pre user g.(user_gname) thr + \post ▷ P ** locked g thr). + + cpp.spec "MyMutex::do_unlock()" as unlock_spec with ( + \this this + \prepost{q P g} this |-> IR g q P + \persist{thr} current_thread thr + \pre locked g thr + \pre ▷P + \post user g.(user_gname) thr). + + (* Axiom *) + cpp.spec "std::this_thread::yield()" as yield_spec with ( + \post emp). + + Import std.atomic. + Fail Lemma test_do_lock_ok : verify[source] "MyMutex::do_lock()". End with_Σ. - End custom_mutex. From 5b1bdda53e8e205ed8f79c4793d749acad26fc1a Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 11:58:22 +0200 Subject: [PATCH 006/109] Tweak spec and client proof --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 6 +++--- rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index c2254964..1bea8673 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -13,9 +13,9 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Export skylabs.brick.libstdcpp.lib.lock_ghost(lockG). Import linearity. -Import lock_ghost. (* TODO UPSTREAM. *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. @@ -30,14 +30,14 @@ Section with_cpp. #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). - (* Section with_RepFor. + Section with_RepFor. Import rep.RepFor. Import RepScheme. #[global] Instance repfor `{!HasStdThreads Σ} {σ : genv} : rep.RepFor.C "std::mutex" [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] (funI γ q P => R γ q P) := {}. - End with_RepFor. *) + End with_RepFor. (** A resource enforcing that the thread calling unlock must be the same thread diff --git a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v index 7f263bf2..53c73e3e 100644 --- a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v @@ -6,6 +6,7 @@ Import linearity. Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. + Context `{!lockG Σ}. cpp.spec "test_mutex()" as test_mutex_spec from source with (\post emp). From 0aa1a82d1e6fa3e68dca5485ec626aade4d7b0fc Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 12:03:16 +0200 Subject: [PATCH 007/109] Drop trailing whitespace --- .../test/mutex/custom_mutex_hpp_proof.v | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 2750bcfc..6e218639 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -12,8 +12,8 @@ Module custom_mutex. Abbreviation N := "MyMutex"%cpp_name. - Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> - (* None if value is thread::id(), Some otherwise *) + Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> + (* None if value is thread::id(), Some otherwise *) option thread_idT -> Rep. Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. @@ -28,7 +28,7 @@ Module custom_mutex. Definition lock_namespace : namespace := nroot .@@ "MyMutex". - Definition locked `{Σ : cpp_logic} `{!lockG Σ} (g: gname) (th : thread_idT) : mpred + Definition locked `{Σ : cpp_logic} `{!lockG Σ} (g: gname) (th : thread_idT) : mpred := owner_token_auth g.(phys_state_gname) (Some th) ** user g.(user_gname) th. (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := @@ -91,7 +91,7 @@ Module custom_mutex. \persist{thr} current_thread thr \pre user g.(user_gname) thr \post ▷ P ** locked g thr). - + cpp.spec "MyMutex::do_unlock()" as unlock_spec with ( \this this \prepost{q P g} this |-> IR g q P @@ -103,7 +103,7 @@ Module custom_mutex. (* Axiom *) cpp.spec "std::this_thread::yield()" as yield_spec with ( \post emp). - + Import std.atomic. Fail Lemma test_do_lock_ok : verify[source] "MyMutex::do_lock()". From 4e0677c0d6403a6e8b130ed86c2018c5270c1edf Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 12:03:25 +0200 Subject: [PATCH 008/109] cleanups --- rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 6e218639..0b6d7068 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -2,11 +2,11 @@ Require Import skylabs.auto.cpp.proof. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. Require Import skylabs.brick.libstdcpp.lib.lock_ghost. -Import lock_ghost. Require Import skylabs.brick.libstdcpp.atomic.spec. +Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. + Module custom_mutex. Abbreviation N := "MyMutex"%cpp_name. From 6981a1a0ac1d00ca16ed0fbf3c56b6bb63fe2ac0 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 13:14:24 +0200 Subject: [PATCH 009/109] Fix parentheses in Rep --- .../test/mutex/custom_mutex_hpp_proof.v | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 0b6d7068..2b06e63b 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -50,17 +50,17 @@ Module custom_mutex. ∃ o_owner, owner_token_frac γ.(phys_state_gname) o_owner ** ∃ b : bool, - this ,, _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m b ** - if b then + this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** + (if b then emp else owner_token_auth γ.(phys_state_gname) o_owner ** - P ** - (** m_owner does not concern do_lock() and do_unlock(), the actual - implementation of mutex, and does not always equal o_owner. - It is just a resource that one can get from the invariant. *) - ∃ m_owner : option thread_idT, - this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m m_owner + P) ** + (** m_owner does not concern do_lock() and do_unlock(), the actual + implementation of mutex, and does not always equal o_owner. + It is just a resource that one can get from the invariant. *) + ∃ m_owner : option thread_idT, + this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m m_owner . Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := From f78f655b7e8f6cef797e9e3bf417ecb79dfb47ea Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 12:02:35 +0200 Subject: [PATCH 010/109] custom_mutex: atomic -> atomic --- rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp index c5844ede..b5bcbf60 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp @@ -16,13 +16,14 @@ std::mutex protocol: - can't be destroyed while held. */ class MyMutex { - std::atomic m_lock{false}; + // std::atomic m_lock{false}; + std::atomic m_lock{0}; std::thread::id m_owner{}; // "Actual locking" void do_lock() { - while (m_lock.exchange(true)) { + while (m_lock.exchange(1)) { // Yielding helps scheduling, and makes this loop obviously not UB // (under https://eel.is/c++draft/intro.progress#1.2). // Calling exchange might qualify under @@ -32,7 +33,7 @@ class MyMutex { } void do_unlock() { - m_lock = false; + m_lock = 0; } public: @@ -60,7 +61,7 @@ class MyMutex { } ~MyMutex() { - assert(!m_lock); + assert(m_lock == 0); assert(m_owner == std::thread::id()); //unowned } }; From bf2b4daa561e94f0e4d0ef0166ced2cd1d74a482 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 Sep 2026 13:15:01 +0200 Subject: [PATCH 011/109] Proof sketch --- .../test/mutex/custom_mutex_hpp_proof.v | 98 ++++++++++++++++++- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 2b06e63b..5169b40a 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -15,6 +15,7 @@ Module custom_mutex. Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) option thread_idT -> Rep. + #[only(timeless)] derive atomic_thread_idT. Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. Parameter owner_token_auth : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. @@ -39,7 +40,7 @@ Module custom_mutex. _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !lockG Σ}. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, !lockG Σ}. Definition mutex_content (γ : gname) : Rep := ∃ o_owner lockedb, @@ -69,11 +70,12 @@ Module custom_mutex. cinv lock_namespace γ.(cinv_gname) (mutex_inv this γ P) ** cinv_own γ.(cinv_gname) q ). + Hint Opaque IR : sl_opacity typeclass_instances. + #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive IR. Context `{MOD : source ⊧ σ}. - Context {HAS_THREADS : HasStdThreads Σ}. cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( \this this @@ -90,6 +92,7 @@ Module custom_mutex. \prepost{q P g} this |-> IR g q P \persist{thr} current_thread thr \pre user g.(user_gname) thr + \prepost{q'} _global "std::memory_order_seq_cst" |-> primR "enum std::memory_order" q' (memory_order.to_val memory_order.seq_cst) \post ▷ P ** locked g thr). cpp.spec "MyMutex::do_unlock()" as unlock_spec with ( @@ -104,8 +107,93 @@ Module custom_mutex. cpp.spec "std::this_thread::yield()" as yield_spec with ( \post emp). - Import std.atomic. - Fail Lemma test_do_lock_ok : verify[source] "MyMutex::do_lock()". - + Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). + + #[only(timeless)] derive exclusive_token. + #[only(timeless)] derive owner_token_auth. + #[only(timeless)] derive owner_token_frac. + + Definition bi_later_exist_F := [FWD] @bi.later_exist. + Definition bi_later_sep_F := [FWD] @bi.later_sep. + Definition bi_later_sep_B := [BWD->] @bi.later_sep. + Hint Resolve bi_later_exist_F bi_later_sep_F : br_hints. + NES.Open std.atomic. + +Import linearity. + + #[program] + Definition do_exchange_C (p : ptr) := + \cancelx + \using denoteModule source + \using{thr} current_thread thr + \consuming{g q P} p |-> IR g q P + \proving{K (_ : IsExistential K)} + std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K + \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** + if bool_decide (res = 0) then P ** owner_token_auth g.(phys_state_gname) (Some thr) else emp) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M ?]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iAuIntro1. rewrite /atomic1_acc. + rewrite {1}/IR/mutex_inv/=; work. + wname [cinv] "#?". + iInv lock_namespace as "?" "Hc"; work. + iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first solve_ndisj. + work. + 1: admit. (* can and should be an AC *) + iMod "Y" as "_"; wpose "Hc"; work. + wname [cinv] "CI". + wname [owner_token_frac] "OG". + wname [_ |-> atomic.R _ _ _] "FL". + wname [_ |-> atomic_thread_idT _ _] "FO". + ren_hyp b bool. + destruct b eqn:?; work. { + iSplitL "OG FO FL"; first last. { + iModIntro. rewrite /IR/mutex_inv. work $usenamed=true. + auto. + } + work $usenamed=true with br_erefl. + iExists _, true. + ework $usenamed=true with br_erefl. + } + wname [P] "P". + wname [owner_token_auth] "OA". + iSplitL "FO OG FL"; first last. { + iModIntro. rewrite /IR/mutex_inv. ework $usenamed=true with br_erefl. + auto. + (* perform ghost update, but earlier *) + admit. + } + work $usenamed=true with br_erefl. + iExists _, true. + ework $usenamed=true with br_erefl. + all: fail. + + (* iSplitL "OG FO I FL"; first last. { *) + (* iModIntro. rewrite /IR/mutex_inv. work $usenamed=true. *) + (* wfocus [| _ |] "". { iPureIntro. destruct b; auto. } *) + (* destruct b eqn:?; work. *) + (* } *) + (* destruct b eqn:?. { *) + (* work $usenamed=true with br_erefl. *) + (* iExists _, true. *) + (* ework $usenamed=true with br_erefl. *) + (* } *) + (* work $usenamed=true with br_erefl. *) + (* iExists _, true. *) + (* ework $usenamed=true with br_erefl. *) + (* iApply affine; last iAccu. *) + Admitted. + Hint Resolve do_exchange_C : sl_opacity. + Hint Opaque locked : sl_opacity. + + Lemma test_do_lock_ok : verify[source] "MyMutex::do_lock()". + Proof using MOD HAS_THREADS. + verify_spec; go. + wp_while (fun _ => emp); go; first by ework. + wp_if; go. + rewrite /locked; go. + Qed. End with_Σ. End custom_mutex. From 8a893e9649d51adc58faf69318ea1c588c0b990a Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 1 Sep 2026 21:35:39 +0000 Subject: [PATCH 012/109] progress on custom mutex proof --- .../test/mutex/custom_mutex_hpp_proof.v | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 5169b40a..88820b2d 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -12,14 +12,15 @@ Module custom_mutex. Abbreviation N := "MyMutex"%cpp_name. - Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> + Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) option thread_idT -> Rep. - #[only(timeless)] derive atomic_thread_idT. + #[only(timeless)] derive thread_idR. - Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. Parameter owner_token_auth : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. Parameter owner_token_frac : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. + #[only(timeless, exclusive)] derive owner_token_auth. + #[only(timeless, exclusive)] derive owner_token_frac. Record gname : Set := MkGname { user_gname : iprop.gname @@ -29,8 +30,10 @@ Module custom_mutex. Definition lock_namespace : namespace := nroot .@@ "MyMutex". - Definition locked `{Σ : cpp_logic} `{!lockG Σ} (g: gname) (th : thread_idT) : mpred - := owner_token_auth g.(phys_state_gname) (Some th) ** user g.(user_gname) th. + Definition locked `{Σ : cpp_logic} `{!lockG Σ} `{σ : genv} + (γ: gname) (o_thr : option thread_idT) : Rep := + _field "MyMutex::m_owner" |-> thread_idR 1$m o_thr ** pureR (owner_token_frac γ.(phys_state_gname) o_thr). + #[only(timeless, exclusive)] derive locked. (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := ∃ x, recursive_mutex.owned_count_id_auth γ.(rec_gname) x. *) @@ -45,23 +48,23 @@ Module custom_mutex. Definition mutex_content (γ : gname) : Rep := ∃ o_owner lockedb, _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m lockedb ** - _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m o_owner. + _field "MyMutex::m_owner" |-> thread_idR 1$m o_owner. Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := - ∃ o_owner, - owner_token_frac γ.(phys_state_gname) o_owner ** ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** + ∃ o_owner : option thread_idT, + owner_token_auth γ.(phys_state_gname) o_owner ** (if b then - emp + ∃ th, user γ.(user_gname) th ** [| o_owner = Some th |] else - owner_token_auth γ.(phys_state_gname) o_owner ** - P) ** - (** m_owner does not concern do_lock() and do_unlock(), the actual - implementation of mutex, and does not always equal o_owner. - It is just a resource that one can get from the invariant. *) - ∃ m_owner : option thread_idT, - this ,, _field "MyMutex::m_owner" |-> atomic_thread_idT 1$m m_owner + (* owner_token γ.(phys_state_gname) o_owner ** *) + P ** + (** m_owner does not concern do_lock() and do_unlock(), the actual + implementation of mutex, and does not always equal o_owner. + It is just a resource that one can get from the invariant. *) + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + owner_token_frac γ.(phys_state_gname) o_owner) . Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := @@ -73,8 +76,6 @@ Module custom_mutex. Hint Opaque IR : sl_opacity typeclass_instances. #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive IR. - - Context `{MOD : source ⊧ σ}. cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( @@ -93,14 +94,16 @@ Module custom_mutex. \persist{thr} current_thread thr \pre user g.(user_gname) thr \prepost{q'} _global "std::memory_order_seq_cst" |-> primR "enum std::memory_order" q' (memory_order.to_val memory_order.seq_cst) - \post ▷ P ** locked g thr). + \post (▷ P ** + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None) ** + owner_token_frac g.(phys_state_gname) (Some thr)). cpp.spec "MyMutex::do_unlock()" as unlock_spec with ( \this this \prepost{q P g} this |-> IR g q P \persist{thr} current_thread thr - \pre locked g thr \pre ▷P + \pre this |-> locked g (Some thr) \post user g.(user_gname) thr). (* Axiom *) @@ -109,10 +112,6 @@ Module custom_mutex. Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). - #[only(timeless)] derive exclusive_token. - #[only(timeless)] derive owner_token_auth. - #[only(timeless)] derive owner_token_frac. - Definition bi_later_exist_F := [FWD] @bi.later_exist. Definition bi_later_sep_F := [FWD] @bi.later_sep. Definition bi_later_sep_B := [BWD->] @bi.later_sep. @@ -127,10 +126,13 @@ Import linearity. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P + \consuming user g.(user_gname) thr \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** owner_token_auth g.(phys_state_gname) (Some thr) else emp) + if bool_decide (res = 0) then P ** owner_token_frac g.(phys_state_gname) (Some thr) ** + p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None + else user g.(user_gname) thr) \end@{mpredI}. Next Obligation. intros. iIntros "[#M ?]" (?? ->). @@ -144,30 +146,32 @@ Import linearity. 1: admit. (* can and should be an AC *) iMod "Y" as "_"; wpose "Hc"; work. wname [cinv] "CI". - wname [owner_token_frac] "OG". + wname [owner_token_auth] "OA". wname [_ |-> atomic.R _ _ _] "FL". - wname [_ |-> atomic_thread_idT _ _] "FO". + wname [_ |-> thread_idR _ _] "PF". + wname [user _ _] "U". ren_hyp b bool. destruct b eqn:?; work. { - iSplitL "OG FO FL"; first last. { + iSplitL "OA FL PF"; first last. { iModIntro. rewrite /IR/mutex_inv. work $usenamed=true. auto. } work $usenamed=true with br_erefl. - iExists _, true. + iExists true, _. ework $usenamed=true with br_erefl. } - wname [P] "P". - wname [owner_token_auth] "OA". - iSplitL "FO OG FL"; first last. { + iDestruct "PF" as "(P & ? & OF)". + iSplitL "FL OA U"; first last. { iModIntro. rewrite /IR/mutex_inv. ework $usenamed=true with br_erefl. auto. (* perform ghost update, but earlier *) admit. } work $usenamed=true with br_erefl. - iExists _, true. + iExists true, _. ework $usenamed=true with br_erefl. + (* should be proved by the previously mentioned ghost update *) + admit. all: fail. (* iSplitL "OG FO I FL"; first last. { *) @@ -193,7 +197,16 @@ Import linearity. verify_spec; go. wp_while (fun _ => emp); go; first by ework. wp_if; go. - rewrite /locked; go. Qed. + + Lemma test_do_unlock_ok : verify[source] "MyMutex::do_unlock()". + Proof using MOD HAS_THREADS. + verify_spec; go. + iExists (user g.(user_gname) thr). + rewrite /locked. + work. + Admitted. + + End with_Σ. End custom_mutex. From ecea73e408508d90ea8e7c26e91a567888801504 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 2 Sep 2026 07:36:34 +0000 Subject: [PATCH 013/109] custom mutex proof mostly done Fix invariant. Add specs for all functions in MyMutex. Not sure if we need do_store_C or do_load_C. --- rocq-brick-libstdcpp/proof/atomic/pred.v | 3 + .../test/mutex/custom_mutex.hpp | 2 + .../test/mutex/custom_mutex_hpp_proof.v | 415 +++++++++++++++--- 3 files changed, 363 insertions(+), 57 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/atomic/pred.v b/rocq-brick-libstdcpp/proof/atomic/pred.v index 52e2a14a..d525684b 100644 --- a/rocq-brick-libstdcpp/proof/atomic/pred.v +++ b/rocq-brick-libstdcpp/proof/atomic/pred.v @@ -5,6 +5,7 @@ * SPDX-License-Identifier: LGPL-2.1 WITH BlueRock Exception for use over network, see repository root for details. *) Require Import skylabs.auto.cpp.spec. +Require Import skylabs.bi.weakly_objective. Require Import skylabs.cpp.spec.concepts. Require skylabs.brick.libstdcpp.atomic.inc_int_cpp. @@ -48,6 +49,8 @@ Module Type ATOMIC_PREDS. #[global] Declare Instance R_frac_valid : CFracValid1 R. #[global] Declare Instance R_agree : Cbn (Learn (any ==> learn_eq ==> learn_hints.fin) R). #[global] Declare Instance R_type : Typed2 (class_name ty) R. + #[global] Declare Instance R_weakly_objective q x p : + WeaklyObjective (R q x p). End R_props. End ATOMIC_PREDS. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp index b5bcbf60..54865952 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp @@ -37,6 +37,8 @@ class MyMutex { } public: + MyMutex() {} + void lock() { std::thread::id this_id{std::this_thread::get_id()}; diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 88820b2d..45b88bc8 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -1,27 +1,122 @@ (** Provisional *) +Require Import iris.algebra.lib.excl_auth. +Require Import iris.algebra.gset. + Require Import skylabs.auto.cpp.proof. +Require Import skylabs.auto.cpp.hints.base_derived. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Require Import skylabs.brick.libstdcpp.atomic.spec. +Require Import skylabs.brick.libstdcpp.cassert.spec. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. Module custom_mutex. - Abbreviation N := "MyMutex"%cpp_name. + #[local] Existing Instance lock_ghost.has_lock. + #[local] Existing Instance lock_ghost.has_lock_upd. + #[local] Existing Instance lock_ghost.has_lock_valid. + Abbreviation N := "MyMutex"%cpp_name. + (* FIXME maybe don't need to split it? *) Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) option thread_idT -> Rep. - #[only(timeless)] derive thread_idR. + #[only(cfracsplittable, type_ptr="std::thread::id")] derive thread_idR. + #[global] Axiom thread_idR_WeaklyObjective : + ∀ `{Σ : cpp_logic, σ : genv} (q : cQp.t) + (o : option thread_idT) (p : ptr), + WeaklyObjective (thread_idR q o p). + #[global] Existing Instance thread_idR_WeaklyObjective. + + #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} + (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : + WeaklyObjective (p |-> R). + Proof. rewrite INTERNAL._at_eq. apply _. Qed. + + Canonical Structure owner_stateO := optionO thread_idTO. + Canonical Structure owner_tokenR := excl_authR owner_stateO. - Parameter owner_token_auth : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. - Parameter owner_token_frac : ∀ `{Σ : cpp_logic}, iprop.gname -> option thread_idT -> mpred. + Class ownerG `{Σ : cpp_logic} := { + #[local] has_owner_token :: HasOwn (iPropI _Σ) owner_tokenR; + #[local] has_owner_token_upd :: HasOwnUpd (iPropI _Σ) owner_tokenR; + #[local] has_owner_token_valid :: HasOwnValid (iPropI _Σ) owner_tokenR; + }. + #[global] Arguments ownerG {_ _} Σ : assert. + + sl.lock + Definition owner_token_auth `{Σ : cpp_logic, !ownerG Σ} + (γ : iprop.gname) (o_thr : option thread_idT) : mpred := + own γ (●E o_thr). + sl.lock + Definition owner_token_frac `{Σ : cpp_logic, !ownerG Σ} + (γ : iprop.gname) (o_thr : option thread_idT) : mpred := + own γ (◯E o_thr). #[only(timeless, exclusive)] derive owner_token_auth. #[only(timeless, exclusive)] derive owner_token_frac. + #[global] Instance owner_token_auth_WeaklyObjective + `{Σ : cpp_logic, !ownerG Σ} γ o_thr : + WeaklyObjective (PROP := iPropI _) (owner_token_auth γ o_thr). + Proof. rewrite owner_token_auth.unlock. apply _. Qed. + + #[global] Instance owner_token_frac_WeaklyObjective + `{Σ : cpp_logic, !ownerG Σ} γ o_thr : + WeaklyObjective (PROP := iPropI _) (owner_token_frac γ o_thr). + Proof. rewrite owner_token_frac.unlock. apply _. Qed. + + Lemma owner_token_agree `{Σ : cpp_logic, !ownerG Σ} + γ (o1 o2 : option thread_idT) : + owner_token_auth γ o1 ** owner_token_frac γ o2 |-- [| o1 = o2 |]. + Proof. + rewrite owner_token_auth.unlock owner_token_frac.unlock. + iIntros "[A F]". + iDestruct (own_valid_2 with "A F") as %HV. + iPureIntro. + apply leibniz_equiv, excl_auth_agree, HV. + Qed. + + #[global] Instance owner_token_agree_observe + `{Σ : cpp_logic, !ownerG Σ} γ (o1 o2 : option thread_idT) : + Observe2 [| o1 = o2 |] + (owner_token_auth γ o1) (owner_token_frac γ o2). + Proof. + apply observe_2_intro_only_provable. + rewrite owner_token_auth.unlock owner_token_frac.unlock. + iIntros "A F". + iDestruct (own_valid_2 with "A F") as %HV. + iPureIntro. + apply leibniz_equiv, excl_auth_agree, HV. + Qed. + + #[global] Instance owner_token_agree_observe_sep + `{Σ : cpp_logic, !ownerG Σ} γ (o1 o2 : option thread_idT) : + Observe [| o1 = o2 |] + (owner_token_auth γ o1 ** owner_token_frac γ o2). + Proof. + apply observe_intro_only_provable. + rewrite owner_token_auth.unlock owner_token_frac.unlock. + iIntros "[A F]". + iDestruct (own_valid_2 with "A F") as %HV. + iPureIntro. + apply leibniz_equiv, excl_auth_agree, HV. + Qed. + + Lemma owner_token_update `{Σ : cpp_logic, !ownerG Σ} + γ (oa ofrag o' : option thread_idT) : + owner_token_auth γ oa ** owner_token_frac γ ofrag |-- + (|==> owner_token_auth γ o' ** owner_token_frac γ o'). + Proof. + rewrite owner_token_auth.unlock owner_token_frac.unlock. + iIntros "[A F]". + iMod (own_update_2 with "A F") as "[$ $]"; + first apply (excl_auth_update _ _ o'). + done. + Qed. + Record gname : Set := MkGname { user_gname : iprop.gname ; cinv_gname : iprop.gname @@ -30,7 +125,8 @@ Module custom_mutex. Definition lock_namespace : namespace := nroot .@@ "MyMutex". - Definition locked `{Σ : cpp_logic} `{!lockG Σ} `{σ : genv} + sl.lock + Definition locked `{Σ : cpp_logic} `{!lockG Σ, !ownerG Σ} `{σ : genv} (γ: gname) (o_thr : option thread_idT) : Rep := _field "MyMutex::m_owner" |-> thread_idR 1$m o_thr ** pureR (owner_token_frac γ.(phys_state_gname) o_thr). #[only(timeless, exclusive)] derive locked. @@ -43,7 +139,11 @@ Module custom_mutex. _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, !lockG Σ}. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, + !lockG Σ, !ownerG Σ}. + + (* Abbreviation BASE p := + (p ,, _base "std::atomic" "std::__atomic_base"). *) Definition mutex_content (γ : gname) : Rep := ∃ o_owner lockedb, @@ -52,7 +152,8 @@ Module custom_mutex. Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, - this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** + this ,, _field "MyMutex::m_lock" |-> + atomic.R "int" 1$m (if b then 1 else 0)%Z ** ∃ o_owner : option thread_idT, owner_token_auth γ.(phys_state_gname) o_owner ** (if b then @@ -78,35 +179,117 @@ Module custom_mutex. Context `{MOD : source ⊧ σ}. + Abbreviation GLOBALS q := + (_global "std::memory_order_seq_cst" |-> + primR "enum std::memory_order" q + (memory_order.to_val memory_order.seq_cst)). + + cpp.spec (default_ctor "std::thread::id") as thread_id_ctor_spec with ( + \this this + \post this |-> thread_idR 1$m None). + + cpp.spec (const_copy_ctor "std::thread::id") as thread_id_copy_ctor_spec with ( + \this this + \arg{other} "" (Vptr other) + \prepost{q o} other |-> thread_idR q o + \post this |-> thread_idR 1$m o). + + cpp.spec (dtor "std::thread::id") as thread_id_dtor_spec with ( + \this this + \pre{o} this |-> thread_idR 1$m o + \post emp). + + cpp.spec "std::thread::id::operator=(const std::thread::id&)" + as thread_id_copy_assign_spec with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{q o} other |-> thread_idR q o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::thread::id::operator=(std::thread::id&&)" + as thread_id_move_assign_spec with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{o} other |-> thread_idR 1$m o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::operator==(std::thread::id, std::thread::id)" + as thread_id_eq_spec with ( + \arg{lhs} "" (Vptr lhs) + \arg{rhs} "" (Vptr rhs) + \prepost{q1 o1} lhs |-> thread_idR q1 o1 + \prepost{q2 o2} rhs |-> thread_idR q2 o2 + \post[Vbool (bool_decide (o1 = o2))] emp). + + cpp.spec "std::this_thread::get_id()" as get_id_spec with ( + \persist{thr} current_thread thr + \post{result}[Vptr result] + result |-> thread_idR 1$m (Some thr)). + cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( \this this \pre{P} ▷P - \post Exists g, this |-> IR g 1$m P ** used_threads g.(user_gname) ∅). + \require WeaklyObjective P + \post (|={⊤}=> Exists g, + this |-> IR g 1$m P ** used_threads g.(user_gname) ∅)). cpp.spec "MyMutex::~MyMutex()" as dtor_spec with ( \this this \pre{g P} this |-> IR g 1$m P ** used_threads g.(user_gname) ∅ \post P). - cpp.spec "MyMutex::do_lock()" as lock_spec with ( + cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( \this this \prepost{q P g} this |-> IR g q P \persist{thr} current_thread thr \pre user g.(user_gname) thr - \prepost{q'} _global "std::memory_order_seq_cst" |-> primR "enum std::memory_order" q' (memory_order.to_val memory_order.seq_cst) - \post (▷ P ** + \prepost{q'} GLOBALS q' + \post (P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None) ** owner_token_frac g.(phys_state_gname) (Some thr)). - cpp.spec "MyMutex::do_unlock()" as unlock_spec with ( + cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this \prepost{q P g} this |-> IR g q P \persist{thr} current_thread thr - \pre ▷P - \pre this |-> locked g (Some thr) + \pre (▷P ** + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None) ** + owner_token_frac g.(phys_state_gname) (Some thr) \post user g.(user_gname) thr). - (* Axiom *) + Definition T : Type := gname * mpred. + + (* FIXME should GLOBALS be in lock specs, instead of do_lock? *) + Definition do_lock (this : ptr) (lk : T) (K : mpred) : mpred := + let g := lk.1 in + let P := lk.2 in + ∃ thr q', current_thread thr ** user g.(user_gname) thr ** GLOBALS q' ** + (GLOBALS q' ** P ** this |-> locked g (Some thr) -* K). + #[global] Arguments do_lock /. + + Definition do_unlock (this : ptr) (lk : T) (K : mpred) : mpred := + let g := lk.1 in + let P := lk.2 in + ∃ thr, current_thread thr ** this |-> locked g (Some thr) ** ▷P ** + (user g.(user_gname) thr -* K). + #[global] Arguments do_unlock /. + + #[global] Instance custom_mutex_basic_lockable : + BasicLockable (T := T) (Tnamed N) + (fun q gP => IR gP.1 q gP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. + + cpp.spec "MyMutex::lock()" as lock_spec with + (\exact Reduce + (lock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + + cpp.spec "MyMutex::unlock()" as unlock_spec with + (\exact Reduce + (unlock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + cpp.spec "std::this_thread::yield()" as yield_spec with ( \post emp). @@ -144,68 +327,186 @@ Import linearity. iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first solve_ndisj. work. 1: admit. (* can and should be an AC *) - iMod "Y" as "_"; wpose "Hc"; work. wname [cinv] "CI". wname [owner_token_auth] "OA". wname [_ |-> atomic.R _ _ _] "FL". wname [_ |-> thread_idR _ _] "PF". wname [user _ _] "U". ren_hyp b bool. - destruct b eqn:?; work. { - iSplitL "OA FL PF"; first last. { - iModIntro. rewrite /IR/mutex_inv. work $usenamed=true. - auto. - } - work $usenamed=true with br_erefl. - iExists true, _. - ework $usenamed=true with br_erefl. - } - iDestruct "PF" as "(P & ? & OF)". - iSplitL "FL OA U"; first last. { - iModIntro. rewrite /IR/mutex_inv. ework $usenamed=true with br_erefl. + destruct b eqn:Hb. + - iMod "Y" as "_". + iMod ("Hc" with "[FL OA PF]") as "_". + { iNext. iExists true. iFrame. } + iModIntro. rewrite /IR. work $usenamed=true with br_erefl. + auto. + - iDestruct "PF" as "(P & FO & OF)". + iMod (owner_token_update g.(phys_state_gname) _ _ (Some thr) + with "[$OA $OF]") as "(OA & OF)". + iMod "Y" as "_". + iMod ("Hc" with "[FL OA U]") as "_". + { iNext. iExists true. iFrame. done. } + iModIntro. rewrite /IR. work $usenamed=true with br_erefl. auto. - (* perform ghost update, but earlier *) - admit. - } - work $usenamed=true with br_erefl. - iExists true, _. - ework $usenamed=true with br_erefl. - (* should be proved by the previously mentioned ghost update *) - admit. - all: fail. - - (* iSplitL "OG FO I FL"; first last. { *) - (* iModIntro. rewrite /IR/mutex_inv. work $usenamed=true. *) - (* wfocus [| _ |] "". { iPureIntro. destruct b; auto. } *) - (* destruct b eqn:?; work. *) - (* } *) - (* destruct b eqn:?. { *) - (* work $usenamed=true with br_erefl. *) - (* iExists _, true. *) - (* ework $usenamed=true with br_erefl. *) - (* } *) - (* work $usenamed=true with br_erefl. *) - (* iExists _, true. *) - (* ework $usenamed=true with br_erefl. *) - (* iApply affine; last iAccu. *) Admitted. Hint Resolve do_exchange_C : sl_opacity. + + #[program] + Definition do_store_C (p : ptr) := + \cancelx + \using denoteModule source + \using{thr} current_thread thr + \consuming{g q P} p |-> IR g q P + \consuming P + \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None + \consuming owner_token_frac g.(phys_state_gname) (Some thr) + \proving{K (_ : IsExistential K)} + std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K + \instantiate K := (p |-> IR g q P ** user g.(user_gname) thr) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M ?]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + rewrite /std.atomic.do_store. + iAcIntro. rewrite /commit_acc /=. + rewrite {1}/IR/mutex_inv /=; work. + wname [cinv] "#?". + iInv lock_namespace as "?" "Hc"; work. + ren_hyp b bool. + iExists (if b then 1%Z else 0%Z). iFrame. + iApply fupd_mask_intro; first solve_ndisj. + iIntros "Y". iNext. iIntros "FL". + iMod "Y" as "_". + wname [P] "P". + wname [_ |-> thread_idR _ _] "FO". + wname [owner_token_frac] "OF". + wname [owner_token_auth] "OA". + iRename "P" into "CI". + wname [P] "RP". + wname [_ |-> thread_idR _ _] "OwnerField". + destruct b eqn:Hb. + - iDestruct "FO" as (owner) "(U & ->)". + iDestruct (observe [| Some owner = Some thr |] + (owner_token_auth g.(phys_state_gname) (Some owner) ** + owner_token_frac g.(phys_state_gname) (Some thr)) + with "[$OA $OF]") + as %Hagree. + inversion Hagree; subst owner. + iEval (rewrite _at_offsetR) in "FL". + iMod ("Hc" with "[FL OA RP OwnerField OF]") as "_". + { iNext. iExists false. + work $usenamed=true with br_erefl. + iExists (Some thr). iFrame. } + iModIntro. rewrite /IR. work $usenamed=true with br_erefl. + - iDestruct "FO" as "(RP2 & OwnerField2 & OF2)". + iEval (rewrite owner_token_frac.unlock) in "OF". + iEval (rewrite owner_token_frac.unlock) in "OF2". + iDestruct (own_valid_2 with "OF OF2") as %Hvalid. + rewrite excl_auth_frag_op_valid in Hvalid. + done. + Qed. + Hint Resolve do_store_C : sl_opacity. + + #[program] + Definition do_load_C (p : ptr) := + \cancelx + \using denoteModule source + \consuming{q (n : Z)} p |-> atomic.R "int" q n + \proving{(K : Z -> mpred) (_ : IsExistential K)} + std.atomic.do_load "int" (BASE p) K + \instantiate K := + (fun x : Z => p |-> atomic.R "int" q n ** [| x = n |]) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M ?]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + rewrite /std.atomic.do_load. + iAcIntro. rewrite /commit_acc. + iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; eauto. + work. iExists q. work. + iMod "Y". iModIntro. + work. + Qed. + Hint Resolve do_load_C : sl_opacity. + Hint Opaque locked : sl_opacity. - Lemma test_do_lock_ok : verify[source] "MyMutex::do_lock()". + Lemma mymutex_do_lock_proof : verify[source] "MyMutex::do_lock()". Proof using MOD HAS_THREADS. verify_spec; go. wp_while (fun _ => emp); go; first by ework. wp_if; go. Qed. - Lemma test_do_unlock_ok : verify[source] "MyMutex::do_unlock()". + Lemma mymutex_do_unlock_proof : verify[source] "MyMutex::do_unlock()". Proof using MOD HAS_THREADS. verify_spec; go. - iExists (user g.(user_gname) thr). - rewrite /locked. + Qed. + + Lemma mymutex_ctor_proof : verify[source] "MyMutex::MyMutex()". + Proof using MOD HAS_THREADS. + verify_spec; go. + wname [structR] "S". + iMod (own_alloc (● (GSet ∅ : lock_ghostUR))) as (gu) "UT". + { apply auth_auth_valid. done. } + iMod (own_alloc (●E None ⋅ ◯E None)) as (gp) "O". + { apply excl_auth_valid. } + iDestruct (own_op with "O") as "(OA & OF)". + iMod (cinv_alloc ⊤ lock_namespace + (mutex_inv this (MkGname gu gu gp) P) with "[-S UT]") + as (gi) "(#CI & CO)"; last first. + - iExists (MkGname gu gi gp). + rewrite /IR used_threads.unlock /=. + iModIntro. go $usenamed=true with br_erefl. + - rewrite /mutex_inv owner_token_auth.unlock + owner_token_frac.unlock /=. + iNext. iExists false. iFrame. + Qed. + + Lemma mymutex_dtor_proof : verify[source] "MyMutex::~MyMutex()". + Proof using MOD HAS_THREADS. + verify_spec. + rewrite /IR /mutex_inv. work. - Admitted. + wname [cinv] "#CI". + wname [cinv_own] "CO". + iMod (cinv_cancel with "CI CO") + as "Inv"; [done..|]. + go. + iDestruct "Inv" as (b) "(Lock & % & OA & State)". + destruct b eqn:Hb. + - iDestruct "State" as (th) "(U & %Eq)". + iDestruct (used_threads_empty_no_not_locked with "[$]") as %[]. + - iDestruct "State" as "(P & Owner & OF)". + ego $usenamed=true with br_erefl. + wname [used_threads] "UT". + iApply (affine with "[OA OF UT]"); last iAccu. apply mpred_BiAffine. + Qed. + + Lemma mymutex_lock_proof : verify[source] "MyMutex::lock()". + Proof using MOD HAS_THREADS. + verify_spec. + rewrite locked.unlock. + ego. + Qed. + + Lemma mymutex_unlock_proof : verify[source] "MyMutex::unlock()". + Proof using MOD HAS_THREADS. + verify_spec. + rewrite locked.unlock. + go. + iExists _, _. + go. + iExists _, _, _, _. + go. + iExists _, _. + go. + iExists _, _. + go. + iExists _. + go. + iExists q, m.2, m.1. + go. + Qed. End with_Σ. From 188ed8ddb039b35f9d931718d7d8c1d0a1b6dfea Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 Sep 2026 13:18:18 +0200 Subject: [PATCH 014/109] Import linearity, drop unused lemmas --- .../test/mutex/custom_mutex_hpp_proof.v | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 45b88bc8..03da0ce8 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -11,6 +11,9 @@ Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Require Import skylabs.brick.libstdcpp.atomic.spec. Require Import skylabs.brick.libstdcpp.cassert.spec. +NES.Open std.atomic. + +Import linearity. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. Module custom_mutex. @@ -68,16 +71,6 @@ Module custom_mutex. WeaklyObjective (PROP := iPropI _) (owner_token_frac γ o_thr). Proof. rewrite owner_token_frac.unlock. apply _. Qed. - Lemma owner_token_agree `{Σ : cpp_logic, !ownerG Σ} - γ (o1 o2 : option thread_idT) : - owner_token_auth γ o1 ** owner_token_frac γ o2 |-- [| o1 = o2 |]. - Proof. - rewrite owner_token_auth.unlock owner_token_frac.unlock. - iIntros "[A F]". - iDestruct (own_valid_2 with "A F") as %HV. - iPureIntro. - apply leibniz_equiv, excl_auth_agree, HV. - Qed. #[global] Instance owner_token_agree_observe `{Σ : cpp_logic, !ownerG Σ} γ (o1 o2 : option thread_idT) : @@ -92,19 +85,6 @@ Module custom_mutex. apply leibniz_equiv, excl_auth_agree, HV. Qed. - #[global] Instance owner_token_agree_observe_sep - `{Σ : cpp_logic, !ownerG Σ} γ (o1 o2 : option thread_idT) : - Observe [| o1 = o2 |] - (owner_token_auth γ o1 ** owner_token_frac γ o2). - Proof. - apply observe_intro_only_provable. - rewrite owner_token_auth.unlock owner_token_frac.unlock. - iIntros "[A F]". - iDestruct (own_valid_2 with "A F") as %HV. - iPureIntro. - apply leibniz_equiv, excl_auth_agree, HV. - Qed. - Lemma owner_token_update `{Σ : cpp_logic, !ownerG Σ} γ (oa ofrag o' : option thread_idT) : owner_token_auth γ oa ** owner_token_frac γ ofrag |-- @@ -145,10 +125,12 @@ Module custom_mutex. (* Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). *) + (* Definition mutex_content (γ : gname) : Rep := ∃ o_owner lockedb, _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m lockedb ** _field "MyMutex::m_owner" |-> thread_idR 1$m o_owner. + *) Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, @@ -299,9 +281,7 @@ Module custom_mutex. Definition bi_later_sep_F := [FWD] @bi.later_sep. Definition bi_later_sep_B := [BWD->] @bi.later_sep. Hint Resolve bi_later_exist_F bi_later_sep_F : br_hints. - NES.Open std.atomic. - -Import linearity. + Import linearity. #[program] Definition do_exchange_C (p : ptr) := From 8d0dcd120a29562d3e1f8a55b8f9a7d20eb11d3d Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 Sep 2026 13:44:34 +0200 Subject: [PATCH 015/109] Proof tweaks --- .../test/mutex/custom_mutex_hpp_proof.v | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 03da0ce8..44de2326 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -313,23 +313,33 @@ Module custom_mutex. wname [_ |-> thread_idR _ _] "PF". wname [user _ _] "U". ren_hyp b bool. + iMod "Y" as "_". destruct b eqn:Hb. - - iMod "Y" as "_". - iMod ("Hc" with "[FL OA PF]") as "_". - { iNext. iExists true. iFrame. } + - iMod ("Hc" with "[FL OA PF]") as "_". + { iExists true. ework $usenamed=true with br_erefl. } iModIntro. rewrite /IR. work $usenamed=true with br_erefl. auto. - iDestruct "PF" as "(P & FO & OF)". iMod (owner_token_update g.(phys_state_gname) _ _ (Some thr) with "[$OA $OF]") as "(OA & OF)". - iMod "Y" as "_". iMod ("Hc" with "[FL OA U]") as "_". - { iNext. iExists true. iFrame. done. } + { iExists true. ework $usenamed=true with br_erefl. } iModIntro. rewrite /IR. work $usenamed=true with br_erefl. auto. Admitted. Hint Resolve do_exchange_C : sl_opacity. + #[global] Instance owner_token_frac_excl g : + Exclusive1 (owner_token_frac g). + Proof. + intros; rewrite observe_2_pure owner_token_frac.unlock. + apply /observe_2_derive_only_provable. + by rewrite excl_auth_frag_op_valid. + Qed. + Import observe2_fwd. + Definition owner_token_frac_excl_F := ltac:(mk_obs2_fwd owner_token_frac_excl). + Hint Resolve owner_token_frac_excl_F : sl_opacity. + #[program] Definition do_store_C (p : ptr) := \cancelx @@ -365,24 +375,20 @@ Module custom_mutex. wname [_ |-> thread_idR _ _] "OwnerField". destruct b eqn:Hb. - iDestruct "FO" as (owner) "(U & ->)". - iDestruct (observe [| Some owner = Some thr |] - (owner_token_auth g.(phys_state_gname) (Some owner) ** - owner_token_frac g.(phys_state_gname) (Some thr)) - with "[$OA $OF]") - as %Hagree. - inversion Hagree; subst owner. + iDestruct (observe_2 [| Some owner = Some thr |] with "OA OF") + as %->%(inj _). iEval (rewrite _at_offsetR) in "FL". iMod ("Hc" with "[FL OA RP OwnerField OF]") as "_". { iNext. iExists false. work $usenamed=true with br_erefl. iExists (Some thr). iFrame. } iModIntro. rewrite /IR. work $usenamed=true with br_erefl. - - iDestruct "FO" as "(RP2 & OwnerField2 & OF2)". - iEval (rewrite owner_token_frac.unlock) in "OF". - iEval (rewrite owner_token_frac.unlock) in "OF2". - iDestruct (own_valid_2 with "OF OF2") as %Hvalid. - rewrite excl_auth_frag_op_valid in Hvalid. - done. + - + (* TODO AUTO *) + Fail by work $usenamed=true. + Succeed by iStopProof; work. + iDestruct "FO" as "?"; iDestruct "OF" as "?"; + work using owner_token_frac_excl_F. Qed. Hint Resolve do_store_C : sl_opacity. From 28ba7a16683311d886f2070dc318aa35a4793cf7 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 Sep 2026 14:17:41 +0200 Subject: [PATCH 016/109] More proof tweaks --- .../test/mutex/custom_mutex_hpp_proof.v | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 44de2326..41e18d34 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -479,19 +479,9 @@ Module custom_mutex. Proof using MOD HAS_THREADS. verify_spec. rewrite locked.unlock. - go. - iExists _, _. - go. - iExists _, _, _, _. - go. - iExists _, _. - go. - iExists _, _. - go. - iExists _. - go. - iExists q, m.2, m.1. - go. + (* TODO AUTO *) + Fail by ego. + repeat (go; ework). Qed. From 2cdf9b11d1c7abc553fc4a330600b8fb36a45895 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 4 Sep 2026 12:39:06 +0200 Subject: [PATCH 017/109] Spec tweaks --- .../test/mutex/custom_mutex_hpp_proof.v | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 41e18d34..6752c7f2 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -228,17 +228,17 @@ Module custom_mutex. \persist{thr} current_thread thr \pre user g.(user_gname) thr \prepost{q'} GLOBALS q' - \post (P ** - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None) ** + \post P ** + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** owner_token_frac g.(phys_state_gname) (Some thr)). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this \prepost{q P g} this |-> IR g q P \persist{thr} current_thread thr - \pre (▷P ** - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None) ** - owner_token_frac g.(phys_state_gname) (Some thr) + \pre ▷P + \pre this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None + \pre owner_token_frac g.(phys_state_gname) (Some thr) \post user g.(user_gname) thr). Definition T : Type := gname * mpred. From ef6652171a0f328034a4f32f1b51194d79726831 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Fri, 4 Sep 2026 16:47:41 +0000 Subject: [PATCH 018/109] change mutex to use a fractional used_threads and another separate piece of exclusive my_mutex --- .../proof/mutex/proof/lock_guard.v | 11 ++- .../proof/mutex/proof/unique_lock.v | 5 +- .../proof/mutex/spec/lock_guard.v | 28 +++---- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 81 +++++++------------ .../proof/mutex/spec/scoped_lock.v | 33 ++++---- 5 files changed, 67 insertions(+), 91 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index a828faae..3005bd17 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -3,14 +3,13 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.mutex.spec.lock_guard. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Import linearity. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!lock_ghost.lockG Σ}. + Context `{!mutex.G Σ}. Import lock_guard. @@ -25,7 +24,9 @@ Section with_cpp. Proof. verify_spec. go. - by rewrite left_id_L. + iExists (mutex.locked g thr qt ** P), qt. + go with br_erefl. + by rewrite (left_id_L 1%Qp Qp.mul). Qed. Lemma dtor_ok : verify[source] dtor_spec. @@ -33,7 +34,9 @@ Section with_cpp. verify_spec. rewrite !R.unlock. go. - by rewrite !left_id_L. + iExists (mutex.not_locked g thr qt), qt. + go with br_erefl. + by rewrite (left_id_L 1%Qp Qp.mul). Qed. End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v index dc8a20d7..5018814c 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v @@ -6,13 +6,12 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.prelude. Require Import skylabs.brick.libstdcpp.mutex.spec.unique_lock. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.lib.tactics. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. NES.Begin unique_lock. Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. - Context `{!lock_ghost.lockG Σ}. + Context `{!mutex.G Σ}. Import R_unfold. @@ -45,7 +44,7 @@ NES.Begin unique_lock. "std::unique_lock::unique_lock(std::mutex&)". Proof. verify_spec; go. - iExists K, q. + iExists K, t, q. (* Time Succeed solve [setoid_rewrite cQp.scale_mut; setoid_rewrite (right_id_L 1%Qp Qp.mul); ego with br_erefl]. *) rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index c95924eb..88456ca8 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -3,7 +3,6 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Import linearity. @@ -17,9 +16,10 @@ End TO_UPSTREAM. Module lock_guard. sl.lock - Definition R `{Σ : cpp_logic, !HasStdThreads Σ} {σ : genv} (mp : ptr * gname * Qp) (q : cQp.t) (P : mpred) : Rep := + Definition R `{Σ : cpp_logic, !HasStdThreads Σ} {σ : genv} + (mp : ptr * mutex.gname * Qp * Qp) (q : cQp.t) (P : mpred) : Rep := structR "std::lock_guard" q ** - let '(mp, g, q') := mp in + let '(mp, g, q', _) := mp in _field "std::lock_guard::_M_device" |-> refR<"std::mutex"> q mp ** pureR ( mp |-> mutex.R g (q * q')$m P). @@ -48,7 +48,7 @@ Module lock_guard. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!lock_ghost.lockG Σ}. + Context `{!mutex.G Σ}. #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) lock_guard.R) := @@ -88,21 +88,21 @@ Section with_cpp. \this this \arg{mp} "m" (Vptr mp) \persist{thr} current_thread thr - \pre{g q P} mp |-> mutex.R g q$m P - \pre lock_ghost.user g thr + \pre{g q qt P} mp |-> mutex.R g q$m P + \pre mutex.not_locked g thr qt \post - this |-> R (mp, g, q) 1$m P ** - P ** mutex.locked g thr + this |-> R (mp, g, q, qt) 1$m P ** + P ** mutex.locked g thr qt ). cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( \this this - \pre{mp g q P} this |-> R (mp, g, q) 1$m P + \pre{mp g q qt P} this |-> R (mp, g, q, qt) 1$m P \persist{thr} current_thread thr - \pre mutex.locked g thr + \pre mutex.locked g thr qt \pre ▷P \post - lock_ghost.user g thr ** + mutex.not_locked g thr qt ** mp |-> mutex.R g q$m P ). @@ -110,10 +110,10 @@ Section with_cpp. Import skylabs.auto.cpp.prelude.proof. - Lemma mutex_borrow mp g P (this : ptr) (q1 q2 : Qp) : - this |-> R (mp, g, (q1 + q2)%Qp) 1$m P |-- + Lemma mutex_borrow mp g P (this : ptr) (q1 q2 qt : Qp) : + this |-> R (mp, g, (q1 + q2)%Qp, qt) 1$m P |-- mp |-> mutex.R g q1$m P ** - this |-> R (mp, g, q2) 1$m P. + this |-> R (mp, g, q2, qt) 1$m P. Proof. rewrite R.unlock. work. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 1bea8673..fdff5902 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -12,15 +12,18 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. -Export skylabs.brick.libstdcpp.lib.lock_ghost(lockG). +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Import linearity. (* TODO UPSTREAM. *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. -Module mutex. +Module Mutex (State : lock_ghost2.MUTEX_STATE). +Include State. + +#[global] Hint Opaque token not_locked locked : sl_opacity typeclass_instances. + Section with_cpp. Context `{Σ : cpp_logic}. @@ -40,56 +43,24 @@ Section with_cpp. End with_RepFor. - (** A resource enforcing that the thread calling unlock must be the same thread - that owns the lock - - << - \persist{th} >={ L_TI } th - \pre{j} mutex_locked g j - test_unlock(std::mutex & m) { - m.unlock(); - } - >> - - this succeeds: - - << - \persist{th} >={ L_TI } th - \pre mutex_locked g th - same test_unlock - >> - *) - Parameter locked : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - gname -> thread_idT -> mpred. - #[only(timeless)] derive locked. - - #[only(exclusive)] derive locked. - - Context `{!lockG Σ}. - - Abbreviation used_threads γ s := - (lock_ghost.used_threads γ s). - - Abbreviation users γ ths := - (lock_ghost.users γ ths). - - Abbreviation user γ th := (users γ {[ th ]}). + Context `{!G Σ}. Context `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Instance locked_learn : Cbn (Learn (req_eq ==> learn_eq ==> learn_hints.fin) locked). + #[global] Instance locked_learn : + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) locked). Proof. solve_learnable. Qed. cpp.spec "std::mutex::mutex()" as ctor_spec with ( \this this \pre{P} ▷P - \post Exists g, this |-> R g 1$m P ** used_threads g ∅). + \post Exists g, this |-> R g 1$m P ** token g 1). cpp.spec "std::mutex::~mutex()" as dtor_spec with ( \this this - \pre{g P} this |-> R g 1$m P ** used_threads g ∅ + \pre{g P} this |-> R g 1$m P ** token g 1 \post P). (* "Inline" version of these specs. *) @@ -97,41 +68,41 @@ Section with_cpp. \this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre user g thr - \post P ** locked g thr). + \pre{qt} not_locked g thr qt + \post P ** locked g thr qt). Definition do_lock (lk : gname * mpred) (K: mpred) : mpred := let g := lk.1 in let P := lk.2 in - ∃ thr, current_thread thr ∗ user g thr ∗ + ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ (* TODO readd *) (* ▷ *) - (locked g thr ** P -* K). + (locked g thr qt ** P -* K). #[global] Arguments do_lock /. cpp.spec "std::mutex::unlock()" as unlock_spec_alt with ( \this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre locked g thr + \pre{qt} locked g thr qt \pre ▷P - \post user g thr). + \post not_locked g thr qt). Definition do_unlock (lk : gname * mpred) (Q : mpred) : mpred := let g := lk.1 in let P := lk.2 in - Exists thr, current_thread thr ** locked g thr ** ▷P ** + Exists thr qt, current_thread thr ** locked g thr qt ** ▷P ** (* TODO readd *) (* ▷ *) - (user g thr -* Q). + (not_locked g thr qt -* Q). #[global] Arguments do_unlock /. cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with ( \this this \prepost{q P g} this |-> R g q P \persist{th} current_thread th - \pre user g th - \post{b}[Vbool b] if b then P ** locked g th else user g th). + \pre{qt} not_locked g th qt + \post{b}[Vbool b] if b then P ** locked g th qt else not_locked g th qt). (* Obtain same specs from (Basic)Lockable. *) (** <> implements [BasicLockable] *) @@ -150,9 +121,9 @@ Section with_cpp. Definition do_try_lock (lk : gname * mpred) (Q : bool -> mpred) : mpred := let g := lk.1 in let P := lk.2 in - ∃ thr, current_thread thr ∗ user g thr ∗ + ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ ∀ b : bool, - (if b then P ** locked g thr else user g thr) -∗ Q b. + (if b then P ** locked g thr qt else not_locked g thr qt) -∗ Q b. #[global] Arguments do_try_lock /. #[global,program] Instance mutex_lockable : Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := @@ -176,4 +147,8 @@ Section with_cpp. iSplit; iApply specify_mono; ework with br_erefl. Qed. End with_cpp. -End mutex. +End Mutex. + +(** The standard instantiation. Clients that need another camera package can + instantiate [Mutex] with any implementation of [MUTEX_STATE]. *) +Module mutex := Mutex lock_ghost2.LockState. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index b8811f8e..90d83f85 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -3,14 +3,13 @@ Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. Module scoped_lock. Section with_cpp. Context `{Σ : cpp_logic}. Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - cQp.t -> list (ptr * gname * Qp * mpred) -> Rep. + cQp.t -> list (ptr * mutex.gname * Qp * Qp * mpred) -> Rep. #[only(type_ptr="std::scoped_lock")] derive R. #[only(cfractional,ascfractional,cfracvalid)] derive R. @@ -18,7 +17,7 @@ Module scoped_lock. Section with_threads. Context {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. - Context `{!lock_ghost.lockG Σ}. + Context `{!mutex.G Σ}. #[global] Instance: LearnEqF1 R := ltac:(solve_learnable). @@ -29,15 +28,15 @@ Module scoped_lock. \this this \persist{thr} current_thread thr \arg{mp1} "" (Vptr mp1) - \pre{g1 q1 P1} mp1 |-> mutex.R g1 q1$m P1 - \pre lock_ghost.user g1 thr + \pre{g1 q1 qt1 P1} mp1 |-> mutex.R g1 q1$m P1 + \pre mutex.not_locked g1 thr qt1 \arg{mp2} "" (Vptr mp2) - \pre{g2 q2 P2} mp2 |-> mutex.R g2 q2$m P2 - \pre lock_ghost.user g2 thr + \pre{g2 q2 qt2 P2} mp2 |-> mutex.R g2 q2$m P2 + \pre mutex.not_locked g2 thr qt2 \post - this |-> R 1$m [ (mp1, g1, q1, P1); (mp2, g2, q2, P2)] ** - P1 ** mutex.locked g1 thr ** - P2 ** mutex.locked g2 thr + this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] ** + P1 ** mutex.locked g1 thr qt1 ** + P2 ** mutex.locked g2 thr qt2 ). cpp.spec "std::scoped_lock<...>::~scoped_lock()" @@ -46,17 +45,17 @@ Module scoped_lock. \persist{thr} current_thread thr \pre{ mp1 mp2 - g1 q1 P1 - g2 q2 P2 + g1 q1 qt1 P1 + g2 q2 qt2 P2 } - this |-> R 1$m [ (mp1, g1, q1, P1); (mp2, g2, q2, P2)] + this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] \pre |> P1 \pre |> P2 - \pre mutex.locked g1 thr - \pre mutex.locked g2 thr + \pre mutex.locked g1 thr qt1 + \pre mutex.locked g2 thr qt2 \post - mp1 |-> mutex.R g1 q1$m P1 ** lock_ghost.user g1 thr ** - mp2 |-> mutex.R g2 q2$m P2 ** lock_ghost.user g2 thr + mp1 |-> mutex.R g1 q1$m P1 ** mutex.not_locked g1 thr qt1 ** + mp2 |-> mutex.R g2 q2$m P2 ** mutex.not_locked g2 thr qt2 ). End with_threads. End with_cpp. From a6271d326b55b44273afafbb9bf70bcb3ffc4334 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Fri, 4 Sep 2026 23:17:46 +0000 Subject: [PATCH 019/109] mutex proof WIP --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 416 ++++++++++++++++++ .../proof/mutex/proof/lock_guard.v | 2 +- .../proof/mutex/spec/lock_guard.v | 4 +- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 15 +- .../proof/mutex/spec/scoped_lock.v | 8 +- .../test/mutex/custom_mutex_hpp_proof.v | 155 ++----- 6 files changed, 469 insertions(+), 131 deletions(-) create mode 100644 rocq-brick-libstdcpp/proof/lib/lock_ghost2.v diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v new file mode 100644 index 00000000..f0508bc5 --- /dev/null +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -0,0 +1,416 @@ +Require Import iris.algebra.agree. +Require Import iris.algebra.frac. +Require Import iris.algebra.gmap. +Require Import iris.algebra.gset. +Require Import iris.algebra.lib.excl_auth. +Require Import iris.algebra.lib.gmap_view. + +Require Import skylabs.auto.cpp.proof. +Require Export skylabs.brick.libstdcpp.runtime.pred. + +Import linearity. + +(** The mutex specification depends on the ghost state through these + interfaces. *) + +Module Type MUTEX_SETS. + Parameter cmraR : cmra. + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Parameter my_mutexes : forall `{Σ : cpp_logic, !G Σ}, + namespace -> iprop.gname -> thread_idT -> gset iprop.gname -> mpred. + +End MUTEX_SETS. + +Module Type MUTEX_TOKENS. + Parameter cmraR : cmra. + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Parameter token : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> Qp -> mpred. + Parameter given_token : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> Qp -> mpred. + + #[global] Declare Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + #[global] Declare Instance given_token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (given_token γ). + #[global] Declare Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + #[global] Declare Instance given_token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). + + Parameter alloc : forall `{Σ : cpp_logic, !G Σ}, + ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. +End MUTEX_TOKENS. + +Module Type MUTEX_STATE. + Declare Module Sets : MUTEX_SETS. + Declare Module Tokens : MUTEX_TOKENS. + Parameter owner_cmraR : cmra. + + Parameter gname : Set. + + Class G `{Σ : cpp_logic} := { + #[global] sets_G :: Sets.G Σ; + #[global] tokens_G :: Tokens.G Σ; + #[local] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; + #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; + #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Parameter owner_auth : forall `{Σ : cpp_logic, !G Σ}, + gname -> option thread_idT -> mpred. + Parameter owner_frag : forall `{Σ : cpp_logic, !G Σ}, + gname -> option thread_idT -> mpred. + + #[global] Declare Instance owner_auth_timeless + `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_auth γ o_thr). + #[global] Declare Instance owner_frag_timeless + `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_frag γ o_thr). + #[global] Declare Instance owner_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_frag γ). + + Parameter token : forall `{Σ : cpp_logic, !G Σ}, + gname -> Qp -> mpred. + Parameter not_locked : forall `{Σ : cpp_logic, !G Σ}, + gname -> thread_idT -> Qp -> iprop.gname -> mpred. + Parameter locked : forall `{Σ : cpp_logic, !G Σ}, + gname -> option thread_idT -> Qp -> mpred. + + #[global] Declare Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + #[global] Declare Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + #[global] Declare Instance locked_timeless + `{Σ : cpp_logic, !G Σ} γ th q : Timeless (locked γ th q). + #[global] Declare Instance locked_exclusive + `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => locked γ th q). +End MUTEX_STATE. + +(** ** Exact per-thread mutex sets *) + +Module MutexSets. + Canonical Structure cmraR : cmra := + gmap_viewR thread_idT (agreeR (leibnizO (gset iprop.gname))). + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Definition mutex_sets_view + (M : gmap thread_idT (gset iprop.gname)) : + gmap thread_idT (agree (leibnizO (gset iprop.gname))) := + (λ X : gset iprop.gname, to_agree X) <$> M. + + sl.lock + Definition mutex_sets_auth `{Σ : cpp_logic, !G Σ} + (γpool : iprop.gname) + (M : gmap thread_idT (gset iprop.gname)) : mpred := + own γpool (gmap_view_auth (DfracOwn 1) (mutex_sets_view M)). + + sl.lock + Definition mutex_sets_frag `{Σ : cpp_logic, !G Σ} + (γpool : iprop.gname) (th : thread_idT) + (M : gset iprop.gname) : mpred := + own γpool + (gmap_view_frag + (V := agreeR (leibnizO (gset iprop.gname))) + th (DfracOwn 1) (to_agree M)). + + sl.lock + Definition my_mutexes_inv `{Σ : cpp_logic, !G Σ} + (γpool : iprop.gname) : mpred := + ∃ M : gmap thread_idT (gset iprop.gname), mutex_sets_auth γpool M. + + Definition my_mutexes `{Σ : cpp_logic, !G Σ} + (N : namespace) (γpool : iprop.gname) (th : thread_idT) + (M : gset iprop.gname) : mpred := + inv N (my_mutexes_inv γpool) ** mutex_sets_frag γpool th M. + + #[only(timeless)] derive mutex_sets_auth. + #[only(timeless)] derive mutex_sets_frag. + #[only(timeless)] derive my_mutexes_inv. + + #[global] Instance my_mutexes_inv_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γpool : + WeaklyObjective (my_mutexes_inv γpool). + Proof. + rewrite my_mutexes_inv.unlock mutex_sets_auth.unlock. apply _. + Qed. + + Section theory. + Context `{Σ : cpp_logic, !G Σ}. + + Lemma mutex_sets_frag_exclusive γpool th M1 M2 : + mutex_sets_frag γpool th M1 ** mutex_sets_frag γpool th M2 |-- False. + Proof. + rewrite mutex_sets_frag.unlock. + iIntros "[H1 H2]". + iDestruct (own_valid_2 with "H1 H2") as %Hvalid. + apply gmap_view_frag_op_valid in Hvalid as [Hfrac _]. + rewrite dfrac_op_own dfrac_valid_own in Hfrac. + exfalso. exact (Qp.not_add_le_l 1 1 Hfrac). + Qed. + + Lemma mutex_sets_update γpool M th S S' : + mutex_sets_auth γpool M ** mutex_sets_frag γpool th S |-- + (|==> mutex_sets_auth γpool (<[th := S']> M) ** + mutex_sets_frag γpool th S'). + Proof. + rewrite mutex_sets_auth.unlock mutex_sets_frag.unlock + /mutex_sets_view fmap_insert. + iIntros "[HA HF]". + iMod (own_update_2 with "HA HF") as "[HA HF]". + { apply (gmap_view_replace + (V := agreeR (leibnizO (gset iprop.gname))) + (mutex_sets_view M) th (to_agree S) (to_agree S')). done. } + iModIntro. iFrame. + Qed. + + Lemma mutex_sets_alloc_thread γpool M th : + M !! th = None -> + mutex_sets_auth γpool M |-- + (|==> mutex_sets_auth γpool (<[th := ∅]> M) ** + mutex_sets_frag γpool th ∅). + Proof. + rewrite mutex_sets_auth.unlock mutex_sets_frag.unlock + /mutex_sets_view fmap_insert. + iIntros (Hfresh) "HA". + iMod (own_update with "HA") as "[HA HF]". + { apply (gmap_view_alloc + (V := agreeR (leibnizO (gset iprop.gname))) + (mutex_sets_view M) th (DfracOwn 1) (to_agree ∅)). + - rewrite lookup_fmap Hfresh. done. + - done. + - done. } + iModIntro. iFrame. + Qed. + + Lemma my_mutexes_exclusive N1 N2 γpool th M1 M2 : + my_mutexes N1 γpool th M1 ** my_mutexes N2 γpool th M2 |-- False. + Proof. + rewrite /my_mutexes. + iIntros "[[_ H1] [_ H2]]". + iApply (mutex_sets_frag_exclusive with "[$H1 $H2]"). + Qed. + + Lemma my_mutexes_alloc N th : + ⊢ |={⊤}=> ∃ γpool, my_mutexes N γpool th ∅. + Proof. + iMod (own_alloc + (gmap_view_auth + (V := agreeR (leibnizO (gset iprop.gname))) + (DfracOwn 1) (mutex_sets_view ∅))) as (γpool) "HA". + { apply gmap_view_auth_valid. } + iAssert (mutex_sets_auth γpool ∅) with "[HA]" as "Hauth". + { rewrite mutex_sets_auth.unlock. iExact "HA". } + have Hfresh : + (∅ : gmap thread_idT (gset iprop.gname)) !! th = None by done. + iMod (mutex_sets_alloc_thread γpool ∅ th Hfresh with "Hauth") + as "[HA HF]". + iMod (inv_alloc N _ (my_mutexes_inv γpool) with "[HA]") as "#Hinv". + { iNext. rewrite my_mutexes_inv.unlock. + iExists ({[th := ∅]} : gmap thread_idT (gset iprop.gname)). + iFrame. } + iModIntro. iExists γpool. rewrite /my_mutexes. iFrame. + Unshelve. all: try done. + Qed. + + Lemma my_mutexes_insert N γpool th M g : + my_mutexes N γpool th M |-- + (|={⊤}=> my_mutexes N γpool th (M ∪ {[g]})). + Proof. + rewrite /my_mutexes. + iIntros "[#Hinv HF]". + iInv N as "Hpool" "Hclose". + rewrite my_mutexes_inv.unlock. + iDestruct "Hpool" as (A) ">HA". + iMod (mutex_sets_update γpool A th M (M ∪ {[g]}) + with "[$HA $HF]") as "[HA HF]". + iMod ("Hclose" with "[HA]") as "_". + { iNext. iExists _. iFrame. } + iModIntro. iFrame "Hinv HF". + Qed. + End theory. +End MutexSets. + +(** ** The fractional token/given-token pair *) + +Module MutexTokens. + Canonical Structure cmraR : cmra := + prodUR (optionUR fracR) (optionUR fracR). + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Definition token `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (q : Qp) : mpred := + own γ (Some q, None). + + Definition given_token `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (q : Qp) : mpred := + own γ (None, Some q). + + #[global] Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + Proof. + intros q1 q2. rewrite /token -own_op /=. done. + Qed. + + #[global] Instance given_token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (given_token γ). + Proof. + intros q1 q2. rewrite /given_token -own_op /=. done. + Qed. + + #[global] Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + Proof. rewrite /token. apply _. Qed. + + #[global] Instance given_token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). + Proof. rewrite /given_token. apply _. Qed. + + Section theory. + Context `{Σ : cpp_logic, !G Σ}. + + Lemma alloc : + ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. + Proof. + iMod (own_alloc + (((Some 1%Qp, None) ⋅ (None, Some 1%Qp)) : cmraR)) as (γ) "H". + { done. } + iModIntro. iExists γ. + rewrite /token /given_token -own_op. iExact "H". + Qed. + End theory. +End MutexTokens. + +(** The concrete state abstracts over mutex sets and tokens, and implements + optional owner state directly. *) +Module MakeMutexState + (Sets0 : MUTEX_SETS) + (Tokens0 : MUTEX_TOKENS) : MUTEX_STATE. + Module Sets := Sets0. + Module Tokens := Tokens0. + + #[local] Existing Instance Tokens.token_fractional. + #[local] Existing Instance Tokens.given_token_fractional. + #[local] Existing Instance Tokens.token_timeless. + #[local] Existing Instance Tokens.given_token_timeless. + + Canonical Structure owner_cmraR : cmra := + excl_authR (optionO thread_idTO). + + Record mutex_gname : Set := MkGname { + pool_namespace : namespace; + pool_gname : iprop.gname; + token_gname : iprop.gname; + owner_gname : iprop.gname; + }. + Definition gname : Set := mutex_gname. + + Class G `{Σ : cpp_logic} := { + #[global] sets_G :: Sets.G Σ; + #[global] tokens_G :: Tokens.G Σ; + #[local] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; + #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; + #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Definition owner_auth `{Σ : cpp_logic, !G Σ} + (γ : gname) (o_thr : option thread_idT) : mpred := + own γ.(owner_gname) ((●E o_thr) : owner_cmraR). + + Definition owner_frag `{Σ : cpp_logic, !G Σ} + (γ : gname) (o_thr : option thread_idT) : mpred := + own γ.(owner_gname) ((◯E o_thr) : owner_cmraR). + + #[global] Hint Opaque owner_auth owner_frag : sl_opacity typeclass_instances. + + #[only(timeless)] derive owner_auth. + #[only(timeless)] derive owner_frag. + + #[global] Instance owner_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_frag γ). + Proof. + intros o_thr1 o_thr2. rewrite /owner_frag. + iIntros "H1 H2". + iDestruct (own_valid_2 with "H1 H2") as %Hvalid. + move: Hvalid. rewrite excl_auth_frag_op_valid. done. + Qed. + + Definition token `{Σ : cpp_logic, !G Σ} + (γ : gname) (q : Qp) : mpred := + Tokens.token γ.(token_gname) q. + + Definition not_locked `{Σ : cpp_logic, !G Σ} + (γ : gname) (th : thread_idT) (q : Qp) + (inv_gname : iprop.gname) : mpred := + Sets.my_mutexes + γ.(pool_namespace) γ.(pool_gname) th {[inv_gname]} ** + Tokens.token γ.(token_gname) q. + + Definition locked `{Σ : cpp_logic, !G Σ} + (γ : gname) (o_thr : option thread_idT) (q : Qp) : mpred := + Tokens.given_token γ.(token_gname) q ** + owner_frag γ o_thr. + + Lemma not_locked_eq `{Σ : cpp_logic, !G Σ} γ th q inv_gname : + not_locked γ th q inv_gname ⊣⊢ + Sets.my_mutexes + γ.(pool_namespace) γ.(pool_gname) th + {[inv_gname]} ** + Tokens.token γ.(token_gname) q. + Proof. done. Qed. + + Lemma locked_eq `{Σ : cpp_logic, !G Σ} γ o_thr q : + locked γ o_thr q ⊣⊢ + Tokens.given_token γ.(token_gname) q ** + owner_frag γ o_thr. + Proof. done. Qed. + + #[global] Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + Proof. rewrite /token. apply Tokens.token_fractional. Qed. + + #[global] Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + Proof. rewrite /token. apply _. Qed. + + #[global] Instance locked_timeless + `{Σ : cpp_logic, !G Σ} γ th q : Timeless (locked γ th q). + Proof. rewrite /locked. apply _. Qed. + + #[global] Instance locked_exclusive + `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => locked γ th q). + Proof. + intros th1 th2. rewrite /locked. + apply _. + Qed. + +End MakeMutexState. + +Module LockState := MakeMutexState MutexSets MutexTokens. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index 3005bd17..d9540e93 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -24,7 +24,7 @@ Section with_cpp. Proof. verify_spec. go. - iExists (mutex.locked g thr qt ** P), qt. + iExists (mutex.locked g (Some thr) qt ** P), qt. go with br_erefl. by rewrite (left_id_L 1%Qp Qp.mul). Qed. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 88456ca8..a9e77ee9 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -92,14 +92,14 @@ Section with_cpp. \pre mutex.not_locked g thr qt \post this |-> R (mp, g, q, qt) 1$m P ** - P ** mutex.locked g thr qt + P ** mutex.locked g (Some thr) qt ). cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( \this this \pre{mp g q qt P} this |-> R (mp, g, q, qt) 1$m P \persist{thr} current_thread thr - \pre mutex.locked g thr qt + \pre mutex.locked g (Some thr) qt \pre ▷P \post mutex.not_locked g thr qt ** diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index fdff5902..3b935717 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -52,7 +52,6 @@ Section with_cpp. Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) locked). Proof. solve_learnable. Qed. - cpp.spec "std::mutex::mutex()" as ctor_spec with ( \this this \pre{P} ▷P @@ -60,7 +59,7 @@ Section with_cpp. cpp.spec "std::mutex::~mutex()" as dtor_spec with ( \this this - \pre{g P} this |-> R g 1$m P ** token g 1 + \pre{g P} this |-> R g 1$m P ** token g.(lock_state_gname) 1 \post P). (* "Inline" version of these specs. *) @@ -69,7 +68,7 @@ Section with_cpp. \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr \pre{qt} not_locked g thr qt - \post P ** locked g thr qt). + \post P ** locked g (Some thr) qt). Definition do_lock (lk : gname * mpred) (K: mpred) : mpred := let g := lk.1 in @@ -77,21 +76,21 @@ Section with_cpp. ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ (* TODO readd *) (* ▷ *) - (locked g thr qt ** P -* K). + (locked g (Some thr) qt ** P -* K). #[global] Arguments do_lock /. cpp.spec "std::mutex::unlock()" as unlock_spec_alt with ( \this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} locked g thr qt + \pre{qt} locked g (Some thr) qt \pre ▷P \post not_locked g thr qt). Definition do_unlock (lk : gname * mpred) (Q : mpred) : mpred := let g := lk.1 in let P := lk.2 in - Exists thr qt, current_thread thr ** locked g thr qt ** ▷P ** + Exists thr qt, current_thread thr ** locked g (Some thr) qt ** ▷P ** (* TODO readd *) (* ▷ *) (not_locked g thr qt -* Q). @@ -102,7 +101,7 @@ Section with_cpp. \prepost{q P g} this |-> R g q P \persist{th} current_thread th \pre{qt} not_locked g th qt - \post{b}[Vbool b] if b then P ** locked g th qt else not_locked g th qt). + \post{b}[Vbool b] if b then P ** locked g (Some th) qt else not_locked g th qt). (* Obtain same specs from (Basic)Lockable. *) (** <> implements [BasicLockable] *) @@ -123,7 +122,7 @@ Section with_cpp. let P := lk.2 in ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ ∀ b : bool, - (if b then P ** locked g thr qt else not_locked g thr qt) -∗ Q b. + (if b then P ** locked g (Some thr) qt else not_locked g thr qt) -∗ Q b. #[global] Arguments do_try_lock /. #[global,program] Instance mutex_lockable : Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index 90d83f85..655d4804 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -35,8 +35,8 @@ Module scoped_lock. \pre mutex.not_locked g2 thr qt2 \post this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] ** - P1 ** mutex.locked g1 thr qt1 ** - P2 ** mutex.locked g2 thr qt2 + P1 ** mutex.locked g1 (Some thr) qt1 ** + P2 ** mutex.locked g2 (Some thr) qt2 ). cpp.spec "std::scoped_lock<...>::~scoped_lock()" @@ -51,8 +51,8 @@ Module scoped_lock. this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] \pre |> P1 \pre |> P2 - \pre mutex.locked g1 thr qt1 - \pre mutex.locked g2 thr qt2 + \pre mutex.locked g1 (Some thr) qt1 + \pre mutex.locked g2 (Some thr) qt2 \post mp1 |-> mutex.R g1 q1$m P1 ** mutex.not_locked g1 thr qt1 ** mp2 |-> mutex.R g2 q2$m P2 ** mutex.not_locked g2 thr qt2 diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 6752c7f2..08c1d975 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -7,21 +7,15 @@ Require Import skylabs.auto.cpp.proof. Require Import skylabs.auto.cpp.hints.base_derived. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.mutex.requirements. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.atomic.spec. Require Import skylabs.brick.libstdcpp.cassert.spec. - NES.Open std.atomic. Import linearity. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. - Module custom_mutex. - #[local] Existing Instance lock_ghost.has_lock. - #[local] Existing Instance lock_ghost.has_lock_upd. - #[local] Existing Instance lock_ghost.has_lock_valid. - Abbreviation N := "MyMutex"%cpp_name. (* FIXME maybe don't need to split it? *) @@ -40,75 +34,18 @@ Module custom_mutex. WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. - Canonical Structure owner_stateO := optionO thread_idTO. - Canonical Structure owner_tokenR := excl_authR owner_stateO. - - Class ownerG `{Σ : cpp_logic} := { - #[local] has_owner_token :: HasOwn (iPropI _Σ) owner_tokenR; - #[local] has_owner_token_upd :: HasOwnUpd (iPropI _Σ) owner_tokenR; - #[local] has_owner_token_valid :: HasOwnValid (iPropI _Σ) owner_tokenR; - }. - #[global] Arguments ownerG {_ _} Σ : assert. - - sl.lock - Definition owner_token_auth `{Σ : cpp_logic, !ownerG Σ} - (γ : iprop.gname) (o_thr : option thread_idT) : mpred := - own γ (●E o_thr). - sl.lock - Definition owner_token_frac `{Σ : cpp_logic, !ownerG Σ} - (γ : iprop.gname) (o_thr : option thread_idT) : mpred := - own γ (◯E o_thr). - #[only(timeless, exclusive)] derive owner_token_auth. - #[only(timeless, exclusive)] derive owner_token_frac. - - #[global] Instance owner_token_auth_WeaklyObjective - `{Σ : cpp_logic, !ownerG Σ} γ o_thr : - WeaklyObjective (PROP := iPropI _) (owner_token_auth γ o_thr). - Proof. rewrite owner_token_auth.unlock. apply _. Qed. - - #[global] Instance owner_token_frac_WeaklyObjective - `{Σ : cpp_logic, !ownerG Σ} γ o_thr : - WeaklyObjective (PROP := iPropI _) (owner_token_frac γ o_thr). - Proof. rewrite owner_token_frac.unlock. apply _. Qed. - - - #[global] Instance owner_token_agree_observe - `{Σ : cpp_logic, !ownerG Σ} γ (o1 o2 : option thread_idT) : - Observe2 [| o1 = o2 |] - (owner_token_auth γ o1) (owner_token_frac γ o2). - Proof. - apply observe_2_intro_only_provable. - rewrite owner_token_auth.unlock owner_token_frac.unlock. - iIntros "A F". - iDestruct (own_valid_2 with "A F") as %HV. - iPureIntro. - apply leibniz_equiv, excl_auth_agree, HV. - Qed. - - Lemma owner_token_update `{Σ : cpp_logic, !ownerG Σ} - γ (oa ofrag o' : option thread_idT) : - owner_token_auth γ oa ** owner_token_frac γ ofrag |-- - (|==> owner_token_auth γ o' ** owner_token_frac γ o'). - Proof. - rewrite owner_token_auth.unlock owner_token_frac.unlock. - iIntros "[A F]". - iMod (own_update_2 with "A F") as "[$ $]"; - first apply (excl_auth_update _ _ o'). - done. - Qed. - Record gname : Set := MkGname - { user_gname : iprop.gname + { lock_state_gname : LockState.gname ; cinv_gname : iprop.gname - ; phys_state_gname : iprop.gname }. Definition lock_namespace : namespace := nroot .@@ "MyMutex". sl.lock - Definition locked `{Σ : cpp_logic} `{!lockG Σ, !ownerG Σ} `{σ : genv} - (γ: gname) (o_thr : option thread_idT) : Rep := - _field "MyMutex::m_owner" |-> thread_idR 1$m o_thr ** pureR (owner_token_frac γ.(phys_state_gname) o_thr). + Definition locked `{Σ : cpp_logic} `{!LockState.G Σ} `{σ : genv} + (γ: gname) (thr : thread_idT) (q : cQp.t) : Rep := + _field "MyMutex::m_owner" |-> thread_idR 1$m (Some thr) ** pureR (LockState.locked γ.(lock_state_gname) (Some thr) q%Qp). + #[global] Hint Opaque locked : sl_opacity typeclass_instances. #[only(timeless, exclusive)] derive locked. (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := @@ -120,35 +57,28 @@ Module custom_mutex. Section with_Σ. Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, - !lockG Σ, !ownerG Σ}. - - (* Abbreviation BASE p := - (p ,, _base "std::atomic" "std::__atomic_base"). *) - - (* - Definition mutex_content (γ : gname) : Rep := - ∃ o_owner lockedb, - _field "MyMutex::m_lock" |-> atomic.R "bool" 1$m lockedb ** - _field "MyMutex::m_owner" |-> thread_idR 1$m o_owner. - *) + !LockState.G Σ}. Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** ∃ o_owner : option thread_idT, - owner_token_auth γ.(phys_state_gname) o_owner ** + LockState.owner_auth γ.(lock_state_gname).(LockState.owner_gname) o_owner ** (if b then - ∃ th, user γ.(user_gname) th ** [| o_owner = Some th |] + ∃ th q1 q2, + LockState.not_locked γ.(lock_state_gname) th q1 γ.(cinv_gname) ** + MutexTokens.given_token γ.(lock_state_gname).(LockState.token_gname) q2 ** + [| (q1 + q2 = 1)%Qp |] else - (* owner_token γ.(phys_state_gname) o_owner ** *) + (* LockState.owner_auth γ.(lock_state_gname) o_owner ** *) P ** (** m_owner does not concern do_lock() and do_unlock(), the actual implementation of mutex, and does not always equal o_owner. It is just a resource that one can get from the invariant. *) this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - owner_token_frac γ.(phys_state_gname) o_owner) - . + LockState.owner_frag γ.(lock_state_gname).(LockState.owner_gname) o_owner ** + MutexTokens.given_token γ.(lock_state_gname).(LockState.token_gname) 1%Qp). Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR N q$m ** @@ -215,62 +145,66 @@ Module custom_mutex. \pre{P} ▷P \require WeaklyObjective P \post (|={⊤}=> Exists g, - this |-> IR g 1$m P ** used_threads g.(user_gname) ∅)). + this |-> IR g 1$m P ** LockState.token g.(lock_state_gname) 1%Qp)). cpp.spec "MyMutex::~MyMutex()" as dtor_spec with ( \this this - \pre{g P} this |-> IR g 1$m P ** used_threads g.(user_gname) ∅ + \pre{g P} this |-> IR g 1$m P ** LockState.token g.(lock_state_gname) 1%Qp \post P). cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( \this this - \prepost{q P g} this |-> IR g q P + \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre user g.(user_gname) thr + \pre LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) \prepost{q'} GLOBALS q' - \post P ** + (* does not have to be q, but easier if it is *) + \post (P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - owner_token_frac g.(phys_state_gname) (Some thr)). + LockState.owner_frag g.(lock_state_gname).(LockState.owner_gname) (Some thr))). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this - \prepost{q P g} this |-> IR g q P + \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr \pre ▷P \pre this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \pre owner_token_frac g.(phys_state_gname) (Some thr) - \post user g.(user_gname) thr). + \pre LockState.owner_frag g.(lock_state_gname).(LockState.owner_gname) (Some thr) + \post LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)). - Definition T : Type := gname * mpred. + Definition T : Type := gname * cQp.t * mpred. - (* FIXME should GLOBALS be in lock specs, instead of do_lock? *) Definition do_lock (this : ptr) (lk : T) (K : mpred) : mpred := - let g := lk.1 in + let g := lk.1.1 in + let q := lk.1.2 in let P := lk.2 in - ∃ thr q', current_thread thr ** user g.(user_gname) thr ** GLOBALS q' ** - (GLOBALS q' ** P ** this |-> locked g (Some thr) -* K). + ∃ thr q', current_thread thr ** + LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) ** + GLOBALS q' ** + (GLOBALS q' ** P ** this |-> locked g thr q -* K). #[global] Arguments do_lock /. Definition do_unlock (this : ptr) (lk : T) (K : mpred) : mpred := - let g := lk.1 in + let g := lk.1.1 in + let q := lk.1.2 in let P := lk.2 in - ∃ thr, current_thread thr ** this |-> locked g (Some thr) ** ▷P ** - (user g.(user_gname) thr -* K). + ∃ thr , current_thread thr ** this |-> locked g thr q ** ▷P ** + (LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) -* K). #[global] Arguments do_unlock /. #[global] Instance custom_mutex_basic_lockable : BasicLockable (T := T) (Tnamed N) - (fun q gP => IR gP.1 q gP.2) := + (fun _ gqP => IR gqP.1.1 gqP.1.2 gqP.2) := { do_lock := do_lock ; do_unlock := do_unlock }. cpp.spec "MyMutex::lock()" as lock_spec with (\exact Reduce - (lock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + (lock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). cpp.spec "MyMutex::unlock()" as unlock_spec with (\exact Reduce - (unlock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + (unlock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). cpp.spec "std::this_thread::yield()" as yield_spec with ( \post emp). @@ -329,17 +263,6 @@ Module custom_mutex. Admitted. Hint Resolve do_exchange_C : sl_opacity. - #[global] Instance owner_token_frac_excl g : - Exclusive1 (owner_token_frac g). - Proof. - intros; rewrite observe_2_pure owner_token_frac.unlock. - apply /observe_2_derive_only_provable. - by rewrite excl_auth_frag_op_valid. - Qed. - Import observe2_fwd. - Definition owner_token_frac_excl_F := ltac:(mk_obs2_fwd owner_token_frac_excl). - Hint Resolve owner_token_frac_excl_F : sl_opacity. - #[program] Definition do_store_C (p : ptr) := \cancelx From 1bdd3eb5975bbe712891e933f83c7d27706e56a7 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Sun, 6 Sep 2026 23:44:10 +0000 Subject: [PATCH 020/109] complete mutex ghost refactor mutex.v now depends on a ghost state module defined in lock_ghosts2.v instead of the user ghost state. The usage is demonstrated in custom_mutex_hpp_proof.v. It has 3 pieces: - my_mutexes records the set of invariant gnames acquired by a thread, and should be allocated by spawn(). A singleton set should be given to a mutex invariant in exchange for the protected resources in it. - a pair of token/given_token. A thread acquiring the resource stores a fraction of token inside the invariant and gets back the same fraction of given_token; destructing lock requires the entire token and thus no thread can hold the lock. - a o_owner_tid, recording the thread ID of the current lock owner. The auth piece is in inv and frag is held by the current lock owner. --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 203 ++++++++++-- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 20 +- .../test/mutex/custom_mutex_hpp_proof.v | 291 +++++++++++------- 3 files changed, 375 insertions(+), 139 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index f0508bc5..9e299025 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -72,17 +72,17 @@ Module Type MUTEX_STATE. }. #[global] Arguments G {_ _} Σ : assert. - Parameter owner_auth : forall `{Σ : cpp_logic, !G Σ}, + Parameter owner_tid_auth : forall `{Σ : cpp_logic, !G Σ}, gname -> option thread_idT -> mpred. - Parameter owner_frag : forall `{Σ : cpp_logic, !G Σ}, + Parameter owner_tid_frag : forall `{Σ : cpp_logic, !G Σ}, gname -> option thread_idT -> mpred. - #[global] Declare Instance owner_auth_timeless - `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_auth γ o_thr). - #[global] Declare Instance owner_frag_timeless - `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_frag γ o_thr). - #[global] Declare Instance owner_frag_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_frag γ). + #[global] Declare Instance owner_tid_auth_timeless + `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_tid_auth γ o_thr). + #[global] Declare Instance owner_tid_frag_timeless + `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_tid_frag γ o_thr). + #[global] Declare Instance owner_tid_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> Qp -> mpred. @@ -155,9 +155,28 @@ Module MutexSets. rewrite my_mutexes_inv.unlock mutex_sets_auth.unlock. apply _. Qed. + #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} N γ th M : + WeaklyObjective (my_mutexes N γ th M). + Proof. rewrite /my_mutexes mutex_sets_frag.unlock. apply _. Qed. + + #[global] Hint Opaque my_mutexes : sl_opacity typeclass_instances. + Section theory. Context `{Σ : cpp_logic, !G Σ}. + Lemma alloc_pool N : + ⊢ |={⊤}=> ∃ γpool, inv N (my_mutexes_inv γpool). + Proof. + iMod (own_alloc + (gmap_view_auth (V := agreeR (leibnizO (gset iprop.gname))) + (DfracOwn 1) (mutex_sets_view ∅))) as (γpool) "HA". + { apply gmap_view_auth_valid. } + iMod (inv_alloc N _ (my_mutexes_inv γpool) with "[HA]") as "#HI". + { iNext. rewrite my_mutexes_inv.unlock mutex_sets_auth.unlock. + iExists ∅. iExact "HA". } + iModIntro. iExists γpool. iExact "HI". + Qed. + Lemma mutex_sets_frag_exclusive γpool th M1 M2 : mutex_sets_frag γpool th M1 ** mutex_sets_frag γpool th M2 |-- False. Proof. @@ -292,9 +311,120 @@ Module MutexTokens. `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). Proof. rewrite /given_token. apply _. Qed. + (** Ordinary and given-token shares in the invariant add up to one. + [token_not_full] contains a positive ordinary-token share, so it conflicts + with the full ordinary token required for destruction. *) + Definition token_not_full `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) : mpred := + token γ 1 ∨ ∃ qt qg : Qp, + [| (qt + qg = 1)%Qp |] ** token γ qt ** given_token γ qg. + + (** Include the endpoint with no ordinary tokens. Transfers preserve the + balance even when a caller returns only part of its given-token share. *) + Definition token_full `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) : mpred := + given_token γ 1 ∨ token_not_full γ. + + #[global] Instance token_full_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ : + WeaklyObjective (token_full γ). + Proof. rewrite /token_full /token_not_full /token /given_token. apply _. Qed. + + #[global] Instance token_not_full_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ : + WeaklyObjective (token_not_full γ). + Proof. rewrite /token_not_full /token /given_token. apply _. Qed. + + #[global] Hint Opaque token given_token token_not_full token_full : sl_opacity typeclass_instances. + Section theory. Context `{Σ : cpp_logic, !G Σ}. + #[local] Existing Instance mpred_BiAffine. + + Lemma token_valid γ q : token γ q |-- [| (q ≤ 1)%Qp |]. + Proof. + rewrite /token. iIntros "H". + iDestruct (own_valid with "H") as %Hvalid. + iPureIntro. exact (proj1 Hvalid). + Qed. + + Lemma given_token_valid γ q : given_token γ q |-- [| (q ≤ 1)%Qp |]. + Proof. + rewrite /given_token. iIntros "H". + iDestruct (own_valid with "H") as %Hvalid. + iPureIntro. exact (proj2 Hvalid). + Qed. + + Lemma token_valid_2 γ q1 q2 : + token γ q1 ** token γ q2 |-- [| (q1 + q2 ≤ 1)%Qp |]. + Proof. rewrite -fractional. apply token_valid. Qed. + + Lemma given_token_valid_2 γ q1 q2 : + given_token γ q1 ** given_token γ q2 |-- [| (q1 + q2 ≤ 1)%Qp |]. + Proof. rewrite -fractional. apply given_token_valid. Qed. + + Lemma token_not_full_full_token γ : + token_not_full γ ** token γ 1 |-- False. + Proof. + rewrite /token_not_full. iIntros "[H T]". + iDestruct "H" as "[H | H]". + - iDestruct (token_valid_2 with "[$T $H]") as %Hbad. + exfalso. exact (Qp.not_add_le_l 1 1 Hbad). + - iDestruct "H" as (qt qg) "(_ & H & _)". + iDestruct (token_valid_2 with "[$T $H]") as %Hbad. + exfalso. exact (Qp.not_add_le_l 1 qt Hbad). + Qed. + + Lemma acquire γ q : + token_full γ ** token γ q |-- given_token γ q ** token_not_full γ. + Proof. + rewrite /token_full /token_not_full. iIntros "[H T]". + iDestruct "H" as "[H | [H | H]]". + - iDestruct (token_valid with "T") as %Hq. + apply Qp.le_lteq in Hq as [Hq | ->]. + + apply Qp.lt_sum in Hq as [r Hr]. + iEval (rewrite Hr fractional) in "H". + iDestruct "H" as "[H R]". iFrame "H". + iRight. iExists q, r. iFrame. done. + + iFrame "H". iLeft. iExact "T". + - iDestruct (token_valid_2 with "[$H $T]") as %Hbad. + exfalso. exact (Qp.not_add_le_l 1 q Hbad). + - iDestruct "H" as (qt qg) "(%Hsum & T0 & G)". + iDestruct (token_valid_2 with "[$T0 $T]") as %Hvalid. + rewrite -Hsum in Hvalid. + apply Qp.add_le_mono_l, Qp.le_lteq in Hvalid as [Hlt | ->]. + + apply Qp.lt_sum in Hlt as [r Hr]. + iEval (rewrite Hr fractional) in "G". + iDestruct "G" as "[G R]". iFrame "G". + iRight. iExists (qt + q)%Qp, r. + iSplit; first (iPureIntro; by rewrite -Qp.add_assoc -Hr). + iFrame "R". rewrite fractional. iFrame. + + iFrame "G". iLeft. rewrite -Hsum fractional. iFrame. + Qed. + + Lemma release γ q : + token_not_full γ ** given_token γ q |-- token γ q ** token_full γ. + Proof. + rewrite /token_full /token_not_full. iIntros "[H G]". + iDestruct "H" as "[H | H]". + - iDestruct (given_token_valid with "G") as %Hq. + apply Qp.le_lteq in Hq as [Hq | ->]. + + apply Qp.lt_sum in Hq as [r Hr]. + iEval (rewrite Hr fractional) in "H". + iDestruct "H" as "[H R]". iFrame "H". + iRight. iRight. iExists r, q. iFrame. + iPureIntro. by rewrite Qp.add_comm. + + iFrame "H". iLeft. iExact "G". + - iDestruct "H" as (qt qg) "(%Hsum & T & G0)". + iDestruct (given_token_valid_2 with "[$G $G0]") as %Hvalid. + rewrite -Hsum in Hvalid. + apply Qp.add_le_mono_r, Qp.le_lteq in Hvalid as [Hlt | ->]. + + apply Qp.lt_sum in Hlt as [r Hr]. + iEval (rewrite Hr fractional) in "T". + iDestruct "T" as "[T R]". iFrame "T". + iRight. iRight. iExists r, (q + qg)%Qp. + iSplit; first (iPureIntro; by rewrite Qp.add_assoc (Qp.add_comm r q) -Hr). + iFrame "R". rewrite fractional. iFrame. + + iFrame "T". iLeft. rewrite -Hsum fractional. iFrame. + Qed. + Lemma alloc : ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. Proof. @@ -311,7 +441,7 @@ End MutexTokens. optional owner state directly. *) Module MakeMutexState (Sets0 : MUTEX_SETS) - (Tokens0 : MUTEX_TOKENS) : MUTEX_STATE. + (Tokens0 : MUTEX_TOKENS) <: MUTEX_STATE. Module Sets := Sets0. Module Tokens := Tokens0. @@ -334,34 +464,61 @@ Module MakeMutexState Class G `{Σ : cpp_logic} := { #[global] sets_G :: Sets.G Σ; #[global] tokens_G :: Tokens.G Σ; - #[local] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; - #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; - #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; + #[global] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; + #[global] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; + #[global] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; }. #[global] Arguments G {_ _} Σ : assert. - Definition owner_auth `{Σ : cpp_logic, !G Σ} + Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} (γ : gname) (o_thr : option thread_idT) : mpred := own γ.(owner_gname) ((●E o_thr) : owner_cmraR). - Definition owner_frag `{Σ : cpp_logic, !G Σ} + Definition owner_tid_frag `{Σ : cpp_logic, !G Σ} (γ : gname) (o_thr : option thread_idT) : mpred := own γ.(owner_gname) ((◯E o_thr) : owner_cmraR). - #[global] Hint Opaque owner_auth owner_frag : sl_opacity typeclass_instances. + #[global] Hint Opaque owner_tid_auth owner_tid_frag : sl_opacity typeclass_instances. - #[only(timeless)] derive owner_auth. - #[only(timeless)] derive owner_frag. + #[only(timeless)] derive owner_tid_auth. + #[only(timeless)] derive owner_tid_frag. - #[global] Instance owner_frag_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_frag γ). + #[global] Instance owner_tid_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). Proof. - intros o_thr1 o_thr2. rewrite /owner_frag. + intros o_thr1 o_thr2. rewrite /owner_tid_frag. iIntros "H1 H2". iDestruct (own_valid_2 with "H1 H2") as %Hvalid. move: Hvalid. rewrite excl_auth_frag_op_valid. done. Qed. + #[global] Instance owner_tid_auth_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ o_thr : + WeaklyObjective (owner_tid_auth γ o_thr). + Proof. rewrite /owner_tid_auth. apply _. Qed. + + #[global] Instance owner_tid_frag_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ o_thr : + WeaklyObjective (owner_tid_frag γ o_thr). + Proof. rewrite /owner_tid_frag. apply _. Qed. + + #[global] Instance owner_agree `{Σ : cpp_logic, !G Σ} γ o1 o2 : + Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). + Proof. + apply observe_2_intro_only_provable. + rewrite /owner_tid_auth /owner_tid_frag. iIntros "A F". + iDestruct (own_valid_2 with "A F") as %HV. + iPureIntro. apply leibniz_equiv, excl_auth_agree, HV. + Qed. + + Lemma owner_update `{Σ : cpp_logic, !G Σ} γ oa ofrag o' : + owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- + (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). + Proof. + rewrite /owner_tid_auth /owner_tid_frag. iIntros "[A F]". + iMod (own_update_2 with "A F") as "[$ $]"; + first apply (excl_auth_update _ _ o'). + done. + Qed. + Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : Qp) : mpred := Tokens.token γ.(token_gname) q. @@ -376,7 +533,7 @@ Module MakeMutexState Definition locked `{Σ : cpp_logic, !G Σ} (γ : gname) (o_thr : option thread_idT) (q : Qp) : mpred := Tokens.given_token γ.(token_gname) q ** - owner_frag γ o_thr. + owner_tid_frag γ o_thr. Lemma not_locked_eq `{Σ : cpp_logic, !G Σ} γ th q inv_gname : not_locked γ th q inv_gname ⊣⊢ @@ -389,7 +546,7 @@ Module MakeMutexState Lemma locked_eq `{Σ : cpp_logic, !G Σ} γ o_thr q : locked γ o_thr q ⊣⊢ Tokens.given_token γ.(token_gname) q ** - owner_frag γ o_thr. + owner_tid_frag γ o_thr. Proof. done. Qed. #[global] Instance token_fractional @@ -411,6 +568,8 @@ Module MakeMutexState apply _. Qed. + #[global] Hint Opaque token not_locked locked : sl_opacity typeclass_instances. + End MakeMutexState. Module LockState := MakeMutexState MutexSets MutexTokens. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 3b935717..67cfba21 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -20,9 +20,21 @@ Import linearity. #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. Module Mutex (State : lock_ghost2.MUTEX_STATE). -Include State. -#[global] Hint Opaque token not_locked locked : sl_opacity typeclass_instances. +(** Pair the abstract state with the invariant named by [State.not_locked]. *) +Record gname : Set := MkGname { + lock_state_gname : State.gname; + cinv_gname : iprop.gname; +}. + +Definition G := @State.G. +Existing Class G. +#[global] Arguments G {_ _} Σ : assert. +#[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : State.G Σ := H. + +Abbreviation token g q:= (State.token g.(lock_state_gname) q). +Abbreviation not_locked g th q := (State.not_locked g.(lock_state_gname) th q g.(cinv_gname)). +Abbreviation locked g o_thr q := (State.locked g.(lock_state_gname) o_thr q). Section with_cpp. Context `{Σ : cpp_logic}. @@ -49,7 +61,7 @@ Section with_cpp. Context {HAS_THREADS : HasStdThreads Σ}. #[global] Instance locked_learn : - Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) locked). + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) State.locked). Proof. solve_learnable. Qed. cpp.spec "std::mutex::mutex()" as ctor_spec with ( @@ -59,7 +71,7 @@ Section with_cpp. cpp.spec "std::mutex::~mutex()" as dtor_spec with ( \this this - \pre{g P} this |-> R g 1$m P ** token g.(lock_state_gname) 1 + \pre{g P} this |-> R g 1$m P ** token g 1 \post P). (* "Inline" version of these specs. *) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 08c1d975..afee9217 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -48,37 +48,32 @@ Module custom_mutex. #[global] Hint Opaque locked : sl_opacity typeclass_instances. #[only(timeless, exclusive)] derive locked. - (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := - ∃ x, recursive_mutex.owned_count_id_auth γ.(rec_gname) x. *) -(* - Definition rawR `{Σ : cpp_logic, σ : genv} (owner : option thread_idT) (count : nat) : Rep := - structR "std::recursive_mutex" 1$m ** - _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) - Section with_Σ. Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, !LockState.G Σ}. + (** The invariant holds the thread's mutex set while the spinlock is held. + Its token balance supports both full and partial ownership transfers. *) Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** ∃ o_owner : option thread_idT, - LockState.owner_auth γ.(lock_state_gname).(LockState.owner_gname) o_owner ** + LockState.owner_tid_auth γ.(lock_state_gname) o_owner ** (if b then - ∃ th q1 q2, - LockState.not_locked γ.(lock_state_gname) th q1 γ.(cinv_gname) ** - MutexTokens.given_token γ.(lock_state_gname).(LockState.token_gname) q2 ** - [| (q1 + q2 = 1)%Qp |] + ∃ th, + MutexSets.my_mutexes + γ.(lock_state_gname).(LockState.pool_namespace) + γ.(lock_state_gname).(LockState.pool_gname) th {[γ.(cinv_gname)]} ** + [| o_owner = Some th |] ** + MutexTokens.token_not_full γ.(lock_state_gname).(LockState.token_gname) else - (* LockState.owner_auth γ.(lock_state_gname) o_owner ** *) P ** - (** m_owner does not concern do_lock() and do_unlock(), the actual - implementation of mutex, and does not always equal o_owner. - It is just a resource that one can get from the invariant. *) + (** The physical owner is cleared before [do_unlock]; the ghost owner + records the last acquiring thread until the next acquisition. *) this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - LockState.owner_frag γ.(lock_state_gname).(LockState.owner_gname) o_owner ** - MutexTokens.given_token γ.(lock_state_gname).(LockState.token_gname) 1%Qp). + LockState.owner_tid_frag γ.(lock_state_gname) o_owner ** + MutexTokens.token_full γ.(lock_state_gname).(LockState.token_gname)). Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR N q$m ** @@ -161,7 +156,7 @@ Module custom_mutex. (* does not have to be q, but easier if it is *) \post (P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - LockState.owner_frag g.(lock_state_gname).(LockState.owner_gname) (Some thr))). + LockState.locked g.(lock_state_gname) (Some thr) q)). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this @@ -169,7 +164,7 @@ Module custom_mutex. \persist{thr} current_thread thr \pre ▷P \pre this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \pre LockState.owner_frag g.(lock_state_gname).(LockState.owner_gname) (Some thr) + \pre LockState.locked g.(lock_state_gname) (Some thr) q \post LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)). Definition T : Type := gname * cQp.t * mpred. @@ -198,6 +193,21 @@ Module custom_mutex. { do_lock := do_lock ; do_unlock := do_unlock }. + cpp.spec "MyMutex::lock()" as lock_spec_alt with ( + \this this + \prepost{g q P} this |-> IR g q P + \persist{thr} current_thread thr + \pre LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) + \prepost{q'} GLOBALS q' + \post P ** this |-> locked g thr q). + + cpp.spec "MyMutex::unlock()" as unlock_spec_alt with ( + \this this + \prepost{g q P} this |-> IR g q P + \persist{thr} current_thread thr + \pre this |-> locked g thr q ** ▷P + \post LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)). + cpp.spec "MyMutex::lock()" as lock_spec with (\exact Reduce (lock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). @@ -206,6 +216,32 @@ Module custom_mutex. (\exact Reduce (unlock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). + Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. + Proof. + iSplit; iApply specify_mono; ework with br_erefl. + lazymatch goal with + | |- environments.envs_entails _ ?Ggoal => + lazymatch Ggoal with + | context[IR ?gqP.1.1 ?gqP.1.2 ?gqP.2] => unify gqP (g, q, P) + end + end. + ework with br_erefl. + Unshelve. all: exact (1$m)%cQp. + Qed. + + Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. + Proof. + iSplit; iApply specify_mono; ework with br_erefl. + lazymatch goal with + | |- environments.envs_entails _ ?Ggoal => + lazymatch Ggoal with + | context[IR ?gqP.1.1 ?gqP.1.2 ?gqP.2] => unify gqP (g, q, P) + end + end. + ework with br_erefl. + Unshelve. all: exact (1$m)%cQp. + Qed. + cpp.spec "std::this_thread::yield()" as yield_spec with ( \post emp). @@ -223,44 +259,60 @@ Module custom_mutex. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P - \consuming user g.(user_gname) thr + \consuming LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** owner_token_frac g.(phys_state_gname) (Some thr) ** + if bool_decide (res = 0) then P ** LockState.locked g.(lock_state_gname) (Some thr) q ** p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - else user g.(user_gname) thr) + else LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)) \end@{mpredI}. Next Obligation. - intros. iIntros "[#M ?]" (?? ->). + intros. iIntros "[#M Hpre]" (?? ->). iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iDestruct "Hpre" as "(#Thr & IR & NL)". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & #CI & CO)". + rewrite /std.atomic.do_exchange. iAuIntro1. rewrite /atomic1_acc. - rewrite {1}/IR/mutex_inv/=; work. - wname [cinv] "#?". - iInv lock_namespace as "?" "Hc"; work. - iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first solve_ndisj. - work. - 1: admit. (* can and should be an AC *) - wname [cinv] "CI". - wname [owner_token_auth] "OA". - wname [_ |-> atomic.R _ _ _] "FL". - wname [_ |-> thread_idR _ _] "PF". - wname [user _ _] "U". - ren_hyp b bool. - iMod "Y" as "_". - destruct b eqn:Hb. - - iMod ("Hc" with "[FL OA PF]") as "_". - { iExists true. ework $usenamed=true with br_erefl. } - iModIntro. rewrite /IR. work $usenamed=true with br_erefl. - auto. - - iDestruct "PF" as "(P & FO & OF)". - iMod (owner_token_update g.(phys_state_gname) _ _ (Some thr) - with "[$OA $OF]") as "(OA & OF)". - iMod ("Hc" with "[FL OA U]") as "_". - { iExists true. ework $usenamed=true with br_erefl. } - iModIntro. rewrite /IR. work $usenamed=true with br_erefl. - auto. - Admitted. + iInv lock_namespace as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (b) "[>L Inv]". + iDestruct "Inv" as (oo) "[>OA State]". + iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first set_solver. + iExists (if b then 1 else 0)%Z. + iSplitL "L". + { ework $usenamed=true with br_erefl. } + iSplit. + - iIntros "L". iMod "Y" as "_". + iMod ("Hclose" with "[L OA State]") as "_". + { iNext. rewrite /mutex_inv. iExists b. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iExists oo. iFrame. } + iModIntro. iFrame. + - iNext. iIntros "L". iMod "Y" as "_". + destruct b. + + iMod ("Hclose" with "[L OA State]") as "_". + { iNext. rewrite /mutex_inv. iExists true. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iExists oo. iFrame. } + iModIntro. rewrite /IR _at_sep _at_as_Rep /=. + iFrame "CI". iFrame. iPureIntro. auto. + + iDestruct "State" as "(P & Owner & OF & Balance)". + iEval (rewrite /LockState.not_locked /LockState.token) in "NL". + iDestruct "NL" as "[Sets T]". + iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". + iMod (LockState.owner_update _ _ _ (Some thr) with "[$OA $OF]") + as "[OA OF]". + iMod ("Hclose" with "[L OA Sets Balance]") as "_". + { iNext. rewrite /mutex_inv. iExists true. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iExists (Some thr). iFrame "OA". + iExists thr. iFrame. done. } + iModIntro. rewrite /IR _at_sep _at_as_Rep /LockState.locked /=. + iFrame "CI". iFrame. iPureIntro. auto. + Qed. Hint Resolve do_exchange_C : sl_opacity. #[program] @@ -271,47 +323,44 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \consuming owner_token_frac g.(phys_state_gname) (Some thr) + \consuming LockState.locked g.(lock_state_gname) (Some thr) q \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K - \instantiate K := (p |-> IR g q P ** user g.(user_gname) thr) + \instantiate K := (p |-> IR g q P ** LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)) \end@{mpredI}. Next Obligation. - intros. iIntros "[#M ?]" (?? ->). + intros. iIntros "[#M Hpre]" (?? ->). iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iDestruct "Hpre" as "(#Thr & IR & P & Owner & Locked)". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & #CI & CO)". + iEval (rewrite /LockState.locked) in "Locked". + iDestruct "Locked" as "[GT OF]". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc /=. - rewrite {1}/IR/mutex_inv /=; work. - wname [cinv] "#?". - iInv lock_namespace as "?" "Hc"; work. - ren_hyp b bool. - iExists (if b then 1%Z else 0%Z). iFrame. - iApply fupd_mask_intro; first solve_ndisj. - iIntros "Y". iNext. iIntros "FL". - iMod "Y" as "_". - wname [P] "P". - wname [_ |-> thread_idR _ _] "FO". - wname [owner_token_frac] "OF". - wname [owner_token_auth] "OA". - iRename "P" into "CI". - wname [P] "RP". - wname [_ |-> thread_idR _ _] "OwnerField". - destruct b eqn:Hb. - - iDestruct "FO" as (owner) "(U & ->)". - iDestruct (observe_2 [| Some owner = Some thr |] with "OA OF") - as %->%(inj _). - iEval (rewrite _at_offsetR) in "FL". - iMod ("Hc" with "[FL OA RP OwnerField OF]") as "_". - { iNext. iExists false. - work $usenamed=true with br_erefl. - iExists (Some thr). iFrame. } - iModIntro. rewrite /IR. work $usenamed=true with br_erefl. - - - (* TODO AUTO *) - Fail by work $usenamed=true. - Succeed by iStopProof; work. - iDestruct "FO" as "?"; iDestruct "OF" as "?"; - work using owner_token_frac_excl_F. + iInv lock_namespace as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (b) "[>L Inv]". + iDestruct "Inv" as (oo) "[>OA State]". + iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first set_solver. + iExists (if b then 1 else 0)%Z. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "L". iMod "Y" as "_". + destruct b. + - iDestruct "State" as (owner) "(Sets & %Heq & Balance)". + iDestruct (observe_2 [| oo = Some thr |] with "OA OF") as %Howner. + have -> : owner = thr by congruence. + iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". + iMod ("Hclose" with "[L OA P Owner OF Balance]") as "_". + { iNext. rewrite /mutex_inv. iExists false. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iExists oo. iFrame. by rewrite Howner. } + iModIntro. + rewrite /IR _at_sep _at_as_Rep /LockState.not_locked /=. + iFrame "CI". iFrame. + - iDestruct "State" as "(P0 & Owner0 & OF0 & Balance0)". + iDestruct (LockState.owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. Hint Resolve do_store_C : sl_opacity. @@ -351,24 +400,43 @@ Module custom_mutex. verify_spec; go. Qed. + Lemma mymutex_lock_alt_proof : verify[source] lock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec; ego. + rewrite locked.unlock. + ego. + Qed. + + Lemma mymutex_unlock_alt_proof : verify[source] unlock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec. + rewrite locked.unlock. + repeat (go; ework). + Qed. + Lemma mymutex_ctor_proof : verify[source] "MyMutex::MyMutex()". Proof using MOD HAS_THREADS. verify_spec; go. wname [structR] "S". - iMod (own_alloc (● (GSet ∅ : lock_ghostUR))) as (gu) "UT". - { apply auth_auth_valid. done. } - iMod (own_alloc (●E None ⋅ ◯E None)) as (gp) "O". + wname [P] "P". + wname [_ |-> atomic.R _ _ _] "L". + wname [_ |-> thread_idR _ _] "Owner". + iMod (MutexSets.alloc_pool (nroot .@@ "MyMutexPool")) as (gp) "#Pool". + iMod (MutexTokens.alloc) as (gt) "[T GT]". + iMod (own_alloc ((●E None ⋅ ◯E None) : LockState.owner_cmraR)) as (go) "O". { apply excl_auth_valid. } - iDestruct (own_op with "O") as "(OA & OF)". - iMod (cinv_alloc ⊤ lock_namespace - (mutex_inv this (MkGname gu gu gp) P) with "[-S UT]") - as (gi) "(#CI & CO)"; last first. - - iExists (MkGname gu gi gp). - rewrite /IR used_threads.unlock /=. - iModIntro. go $usenamed=true with br_erefl. - - rewrite /mutex_inv owner_token_auth.unlock - owner_token_frac.unlock /=. - iNext. iExists false. iFrame. + iDestruct (own_op with "O") as "[OA OF]". + pose (gs := LockState.MkGname (nroot .@@ "MyMutexPool") gp gt go). + iMod (cinv_alloc_cofinite ∅ ⊤ lock_namespace) as (gi) "(_ & CO & Halloc)". + iMod ("Halloc" $! (mutex_inv this (MkGname gs gi) P) + with "[] [L P Owner OA OF GT]") as "#CI". + { iPureIntro. rewrite /mutex_inv. apply _. } + { iNext. rewrite /mutex_inv /=. iExists false. iFrame "L". + iExists None. rewrite /LockState.owner_tid_auth /LockState.owner_tid_frag /gs /=. + iFrame. rewrite /MutexTokens.token_full. iLeft. iExact "GT". } + iModIntro. iExists (MkGname gs gi). + rewrite /IR _at_sep _at_as_Rep /LockState.token /gs /=. + iFrame "CI". iFrame. Qed. Lemma mymutex_dtor_proof : verify[source] "MyMutex::~MyMutex()". @@ -378,33 +446,30 @@ Module custom_mutex. work. wname [cinv] "#CI". wname [cinv_own] "CO". + wname [LockState.token] "T". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. go. iDestruct "Inv" as (b) "(Lock & % & OA & State)". destruct b eqn:Hb. - - iDestruct "State" as (th) "(U & %Eq)". - iDestruct (used_threads_empty_no_not_locked with "[$]") as %[]. - - iDestruct "State" as "(P & Owner & OF)". + - iDestruct "State" as (th) "(Sets & %Eq & Balance)". + iEval (rewrite /LockState.token) in "T". + iDestruct (MutexTokens.token_not_full_full_token with "[$Balance $T]") as %[]. + - iDestruct "State" as "(P & Owner & OF & Balance)". ego $usenamed=true with br_erefl. - wname [used_threads] "UT". - iApply (affine with "[OA OF UT]"); last iAccu. apply mpred_BiAffine. - Qed. + iApply (affine with "[OA OF Balance T]"); last iAccu. apply mpred_BiAffine. + Qed. - Lemma mymutex_lock_proof : verify[source] "MyMutex::lock()". + Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. - verify_spec. - rewrite locked.unlock. - ego. + rewrite lock_spec_entails_lock_spec_alt. + exact mymutex_lock_alt_proof. Qed. - Lemma mymutex_unlock_proof : verify[source] "MyMutex::unlock()". + Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. - verify_spec. - rewrite locked.unlock. - (* TODO AUTO *) - Fail by ego. - repeat (go; ework). + rewrite unlock_spec_entails_unlock_spec_alt. + exact mymutex_unlock_alt_proof. Qed. From 0299a7fa434b47cee81f0774fd732f1cea389ddc Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 7 Sep 2026 08:21:12 +0000 Subject: [PATCH 021/109] refactor mutex specs, std::mutex and custom mutex are both implemntation of the spec module. --- .../proof/mutex/proof/lock_guard.v | 4 +- .../proof/mutex/spec/lock_guard.v | 8 +- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 277 ++++++++++++------ .../proof/mutex/spec/scoped_lock.v | 16 +- .../test/mutex/custom_mutex_hpp_proof.v | 251 +++++++--------- 5 files changed, 318 insertions(+), 238 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index d9540e93..718b9d50 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -24,7 +24,7 @@ Section with_cpp. Proof. verify_spec. go. - iExists (mutex.locked g (Some thr) qt ** P), qt. + iExists (mutex.locked mp g thr qt ** P), qt. go with br_erefl. by rewrite (left_id_L 1%Qp Qp.mul). Qed. @@ -34,7 +34,7 @@ Section with_cpp. verify_spec. rewrite !R.unlock. go. - iExists (mutex.not_locked g thr qt), qt. + iExists (mutex.not_locked mp g thr qt), qt. go with br_erefl. by rewrite (left_id_L 1%Qp Qp.mul). Qed. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index a9e77ee9..48095ffd 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -89,20 +89,20 @@ Section with_cpp. \arg{mp} "m" (Vptr mp) \persist{thr} current_thread thr \pre{g q qt P} mp |-> mutex.R g q$m P - \pre mutex.not_locked g thr qt + \pre mutex.not_locked mp g thr qt \post this |-> R (mp, g, q, qt) 1$m P ** - P ** mutex.locked g (Some thr) qt + P ** mutex.locked mp g thr qt ). cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( \this this \pre{mp g q qt P} this |-> R (mp, g, q, qt) 1$m P \persist{thr} current_thread thr - \pre mutex.locked g (Some thr) qt + \pre mutex.locked mp g thr qt \pre ▷P \post - mutex.not_locked g thr qt ** + mutex.not_locked mp g thr qt ** mp |-> mutex.R g q$m P ). diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 67cfba21..233ceabf 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -19,22 +19,168 @@ Import linearity. (* TODO UPSTREAM. *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. +Module mutex_spec (State : lock_ghost2.MUTEX_STATE). +Section with_cpp. + Context `{Σ : cpp_logic} {Name : Type}. + Context `{!State.G Σ}. + Context (R : Name -> cQp.t -> mpred -> Rep). + Context (state_name : Name -> State.gname). + Context {HAS_THREADS : HasStdThreads Σ} {Q : Type}. + (** FIXME is there a more modular way to provide not_locked, locked and ghost + names, e.g. make them parameters to the mutex_spec module? *) + Context (not_locked locked : ptr -> Name -> thread_idT -> Q -> mpred). + + (** The guarded predicate must be weakly objective for invariant allocation, + which R likely has. *) + Definition ctor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{P} ▷P ** [| WeaklyObjective P |] + \post |={⊤}=> Exists g, this |-> R g 1$m P ** State.token (state_name g) 1). + + Definition dtor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{g P} this |-> R g 1$m P ** State.token (state_name g) 1 + \post P). + + Definition lock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post P ** locked this g thr qt). + + Definition unlock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} locked this g thr qt + \pre ▷P + \post not_locked this g thr qt). + + Definition try_lock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post{b}[Vbool b] + if b then P ** locked this g thr qt else not_locked this g thr qt). + + (* TODO readd the later on the lock/unlock continuations. *) + Definition do_lock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + (locked this lk.1 thr qt ** lk.2 -* K). + #[global] Arguments do_lock /. + + Definition do_unlock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** locked this lk.1 thr qt ** ▷lk.2 ** + (not_locked this lk.1 thr qt -* K). + #[global] Arguments do_unlock /. + + Definition do_try_lock (this : ptr) (lk : Name * mpred) + (K : bool -> mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + ∀ b : bool, + (if b then lk.2 ** locked this lk.1 thr qt + else not_locked this lk.1 thr qt) -* K b. + #[global] Arguments do_try_lock /. + + Section equivalences. + Context {σ : genv}. + (* Context (not_locked locked : ptr -> Name -> thread_idT -> Q -> mpred). *) + Context (method_name class_name : globname). + Context {BL : BasicLockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. + + Definition spec_type_void_to_ret := (fun ret => specify {| info_name := method_name; info_type := tMethod class_name QM ret [] |}). + + Local Lemma method_spec_equiv (ret : type) + (Pspec Qspec : ptr -> WpSpec mpred val val) + (Heq : forall this xs K, Pspec this xs K ⊣⊢ Qspec this xs K) : + spec_type_void_to_ret ret Pspec ⊣⊢ spec_type_void_to_ret ret Qspec. + Proof. + iSplit; iApply specify_mono; intros this xs K; rewrite Heq; done. + Qed. + + Lemma lock_spec_entails_lock_spec_alt + (Hlock : requirements.do_lock (Tnamed class_name) = do_lock) : + spec_type_void_to_ret "void" + (lock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ + spec_type_void_to_ret "void" lock_spec_alt. + Proof. + apply method_spec_equiv. intros this xs K. + unfold lock_basic_lockable. rewrite Hlock. + unfold lock_spec_alt, do_lock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HNL & HK)". + iExists q, (g, P), (P ** locked this g thr qt)%I. + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HNL". + iIntros "[HL HP]". iFrame. + Qed. + + Lemma unlock_spec_entails_unlock_spec_alt + (Hunlock : requirements.do_unlock (Tnamed class_name) = do_unlock) : + spec_type_void_to_ret "void" (unlock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ + spec_type_void_to_ret "void" unlock_spec_alt. + Proof. + apply method_spec_equiv. intros this xs K. + unfold unlock_basic_lockable. rewrite Hunlock. + unfold unlock_spec_alt, do_unlock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HL & HP & HK)". + iExists q, (g, P), (not_locked this g thr qt). + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HL HP". + iIntros "$". + Qed. + + Context {L : Lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. + + Lemma try_lock_spec_entails_try_lock_spec_alt + (Htry_lock : requirements.do_try_lock (Tnamed class_name) = do_try_lock) : + spec_type_void_to_ret "bool" (try_lock_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ + spec_type_void_to_ret "bool" try_lock_spec_alt. + Proof. + apply method_spec_equiv. intros this xs K. + unfold try_lock_lockable. rewrite Htry_lock. + unfold try_lock_spec_alt, do_try_lock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HNL & HK)". + iExists q, (g, P), (fun b : bool => + if b then (P ** locked this g thr qt)%I else not_locked this g thr qt). + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HNL". + iIntros (b) "$". + Qed. + End equivalences. +End with_cpp. +End mutex_spec. + + +(** Specialize the reusable specs to the standard mutex representation and + bind them to their C++ names. *) Module Mutex (State : lock_ghost2.MUTEX_STATE). + Module Spec := mutex_spec State. -(** Pair the abstract state with the invariant named by [State.not_locked]. *) -Record gname : Set := MkGname { - lock_state_gname : State.gname; - cinv_gname : iprop.gname; -}. + (** Pair the abstract state with the invariant named by [State.not_locked]. *) + Record gname : Set := MkGname { + lock_state_gname : State.gname; + cinv_gname : iprop.gname; + }. -Definition G := @State.G. -Existing Class G. -#[global] Arguments G {_ _} Σ : assert. -#[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : State.G Σ := H. + Definition G := @State.G. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : State.G Σ := H. -Abbreviation token g q:= (State.token g.(lock_state_gname) q). -Abbreviation not_locked g th q := (State.not_locked g.(lock_state_gname) th q g.(cinv_gname)). -Abbreviation locked g o_thr q := (State.locked g.(lock_state_gname) o_thr q). + Abbreviation token := (fun g q => State.token g.(lock_state_gname) q). + Abbreviation not_locked := (fun (_ : ptr) g th q => State.not_locked g.(lock_state_gname) th q g.(cinv_gname)). + Abbreviation locked := (fun (_ : ptr) g thr q => State.locked g.(lock_state_gname) (Some thr) q). Section with_cpp. Context `{Σ : cpp_logic}. @@ -64,102 +210,67 @@ Section with_cpp. Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) State.locked). Proof. solve_learnable. Qed. - cpp.spec "std::mutex::mutex()" as ctor_spec with ( - \this this - \pre{P} ▷P - \post Exists g, this |-> R g 1$m P ** token g 1). - - cpp.spec "std::mutex::~mutex()" as dtor_spec with ( - \this this - \pre{g P} this |-> R g 1$m P ** token g 1 - \post P). - - (* "Inline" version of these specs. *) - cpp.spec "std::mutex::lock()" as lock_spec_alt with ( - \this this - \prepost{q P g} this |-> R g q P - \persist{thr} current_thread thr - \pre{qt} not_locked g thr qt - \post P ** locked g (Some thr) qt). - - Definition do_lock (lk : gname * mpred) (K: mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ - (* TODO readd *) - (* ▷ *) - (locked g (Some thr) qt ** P -* K). - #[global] Arguments do_lock /. + cpp.spec "std::mutex::mutex()" as ctor_spec with + (\exact Reduce (Spec.ctor_spec R lock_state_gname)). - cpp.spec "std::mutex::unlock()" as unlock_spec_alt with ( - \this this - \prepost{q P g} this |-> R g q P - \persist{thr} current_thread thr - \pre{qt} locked g (Some thr) qt - \pre ▷P - \post not_locked g thr qt). - - Definition do_unlock (lk : gname * mpred) (Q : mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - Exists thr qt, current_thread thr ** locked g (Some thr) qt ** ▷P ** - (* TODO readd *) - (* ▷ *) - (not_locked g thr qt -* Q). - #[global] Arguments do_unlock /. + cpp.spec "std::mutex::~mutex()" as dtor_spec with + (\exact Reduce (Spec.dtor_spec R lock_state_gname)). + + cpp.spec "std::mutex::lock()" as lock_spec_alt with + (\exact Reduce (Spec.lock_spec_alt R not_locked locked)). - cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with ( - \this this - \prepost{q P g} this |-> R g q P - \persist{th} current_thread th - \pre{qt} not_locked g th qt - \post{b}[Vbool b] if b then P ** locked g (Some th) qt else not_locked g th qt). + cpp.spec "std::mutex::unlock()" as unlock_spec_alt with + (\exact Reduce (Spec.unlock_spec_alt R not_locked locked)). - (* Obtain same specs from (Basic)Lockable. *) - (** <> implements [BasicLockable] *) + cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with + (\exact Reduce (Spec.try_lock_spec_alt R not_locked locked)). + + Definition do_lock := Spec.do_lock not_locked locked. + #[global] Arguments do_lock /. + Definition do_unlock := Spec.do_unlock not_locked locked. + #[global] Arguments do_unlock /. + Definition do_try_lock := Spec.do_try_lock not_locked locked. + #[global] Arguments do_try_lock /. + + (** <> implements [BasicLockable] and [Lockable]. *) Definition T : Type := gname * mpred. - #[global] Instance mutex_basic_lockable : BasicLockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_lock := fun this => do_lock - ; do_unlock := fun this => do_unlock }. + #[global] Instance mutex_basic_lockable : + BasicLockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. cpp.spec "std::mutex::lock()" as lock_spec with - (\exact Reduce (lock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + (\exact Reduce (lock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). cpp.spec "std::mutex::unlock()" as unlock_spec with - (\exact Reduce (unlock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - - Definition do_try_lock (lk : gname * mpred) (Q : bool -> mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - ∃ thr qt, current_thread thr ∗ not_locked g thr qt ∗ - ∀ b : bool, - (if b then P ** locked g (Some thr) qt else not_locked g thr qt) -∗ Q b. - #[global] Arguments do_try_lock /. + (\exact Reduce (unlock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - #[global,program] Instance mutex_lockable : Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_try_lock := fun this => do_try_lock }. + #[global] Instance mutex_lockable : + Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := + { do_try_lock := do_try_lock }. cpp.spec "std::mutex::try_lock()" as try_lock_spec with - (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. Proof. - iSplit; iApply specify_mono; ework with br_erefl. + apply (Spec.lock_spec_entails_lock_spec_alt R not_locked locked). + reflexivity. Qed. Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. Proof. - iSplit; iApply specify_mono; ework with br_erefl. + apply (Spec.unlock_spec_entails_unlock_spec_alt R not_locked locked). + reflexivity. Qed. Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. Proof. - iSplit; iApply specify_mono; ework with br_erefl. + apply (Spec.try_lock_spec_entails_try_lock_spec_alt R not_locked locked). + reflexivity. Qed. End with_cpp. End Mutex. -(** The standard instantiation. Clients that need another camera package can - instantiate [Mutex] with any implementation of [MUTEX_STATE]. *) Module mutex := Mutex lock_ghost2.LockState. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index 655d4804..9a317ffa 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -29,14 +29,14 @@ Module scoped_lock. \persist{thr} current_thread thr \arg{mp1} "" (Vptr mp1) \pre{g1 q1 qt1 P1} mp1 |-> mutex.R g1 q1$m P1 - \pre mutex.not_locked g1 thr qt1 + \pre mutex.not_locked mp1 g1 thr qt1 \arg{mp2} "" (Vptr mp2) \pre{g2 q2 qt2 P2} mp2 |-> mutex.R g2 q2$m P2 - \pre mutex.not_locked g2 thr qt2 + \pre mutex.not_locked mp2 g2 thr qt2 \post this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] ** - P1 ** mutex.locked g1 (Some thr) qt1 ** - P2 ** mutex.locked g2 (Some thr) qt2 + P1 ** mutex.locked mp1 g1 thr qt1 ** + P2 ** mutex.locked mp2 g2 thr qt2 ). cpp.spec "std::scoped_lock<...>::~scoped_lock()" @@ -51,11 +51,11 @@ Module scoped_lock. this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] \pre |> P1 \pre |> P2 - \pre mutex.locked g1 (Some thr) qt1 - \pre mutex.locked g2 (Some thr) qt2 + \pre mutex.locked mp1 g1 thr qt1 + \pre mutex.locked mp2 g2 thr qt2 \post - mp1 |-> mutex.R g1 q1$m P1 ** mutex.not_locked g1 thr qt1 ** - mp2 |-> mutex.R g2 q2$m P2 ** mutex.not_locked g2 thr qt2 + mp1 |-> mutex.R g1 q1$m P1 ** mutex.not_locked mp1 g1 thr qt1 ** + mp2 |-> mutex.R g2 q2$m P2 ** mutex.not_locked mp2 g2 thr qt2 ). End with_threads. End with_cpp. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index afee9217..e9ad325a 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -14,11 +14,13 @@ NES.Open std.atomic. Import linearity. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. + Module custom_mutex. + Module Spec := mutex_spec LockState. + Abbreviation N := "MyMutex"%cpp_name. - (* FIXME maybe don't need to split it? *) Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) option thread_idT -> Rep. @@ -34,6 +36,59 @@ Module custom_mutex. WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. + (* thread::id operations and yield() are not proved for now. *) + Section unproved_specs. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ}. + Context `{MOD : source ⊧ σ}. + + cpp.spec (default_ctor "std::thread::id") as thread_id_ctor_spec with ( + \this this + \post this |-> thread_idR 1$m None). + + cpp.spec (const_copy_ctor "std::thread::id") as thread_id_copy_ctor_spec with ( + \this this + \arg{other} "" (Vptr other) + \prepost{q o} other |-> thread_idR q o + \post this |-> thread_idR 1$m o). + + cpp.spec (dtor "std::thread::id") as thread_id_dtor_spec with ( + \this this + \pre{o} this |-> thread_idR 1$m o + \post emp). + + cpp.spec "std::thread::id::operator=(const std::thread::id&)" + as thread_id_copy_assign_spec with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{q o} other |-> thread_idR q o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::thread::id::operator=(std::thread::id&&)" + as thread_id_move_assign_spec with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{o} other |-> thread_idR 1$m o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::operator==(std::thread::id, std::thread::id)" + as thread_id_eq_spec with ( + \arg{lhs} "" (Vptr lhs) + \arg{rhs} "" (Vptr rhs) + \prepost{q1 o1} lhs |-> thread_idR q1 o1 + \prepost{q2 o2} rhs |-> thread_idR q2 o2 + \post[Vbool (bool_decide (o1 = o2))] emp). + + cpp.spec "std::this_thread::get_id()" as get_id_spec with ( + \persist{thr} current_thread thr + \post{result}[Vptr result] + result |-> thread_idR 1$m (Some thr)). + + cpp.spec "std::this_thread::yield()" as yield_spec with ( + \post emp). + End unproved_specs. + Record gname : Set := MkGname { lock_state_gname : LockState.gname ; cinv_gname : iprop.gname @@ -42,11 +97,11 @@ Module custom_mutex. Definition lock_namespace : namespace := nroot .@@ "MyMutex". sl.lock - Definition locked `{Σ : cpp_logic} `{!LockState.G Σ} `{σ : genv} + Definition lockedR `{Σ : cpp_logic} `{!LockState.G Σ} `{σ : genv} (γ: gname) (thr : thread_idT) (q : cQp.t) : Rep := _field "MyMutex::m_owner" |-> thread_idR 1$m (Some thr) ** pureR (LockState.locked γ.(lock_state_gname) (Some thr) q%Qp). - #[global] Hint Opaque locked : sl_opacity typeclass_instances. - #[only(timeless, exclusive)] derive locked. + #[global] Hint Opaque lockedR : sl_opacity typeclass_instances. + #[only(timeless, exclusive)] derive lockedR. Section with_Σ. Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, @@ -91,100 +146,49 @@ Module custom_mutex. primR "enum std::memory_order" q (memory_order.to_val memory_order.seq_cst)). - cpp.spec (default_ctor "std::thread::id") as thread_id_ctor_spec with ( - \this this - \post this |-> thread_idR 1$m None). + Abbreviation token := (fun g q => LockState.token g.(lock_state_gname) q). - cpp.spec (const_copy_ctor "std::thread::id") as thread_id_copy_ctor_spec with ( - \this this - \arg{other} "" (Vptr other) - \prepost{q o} other |-> thread_idR q o - \post this |-> thread_idR 1$m o). - - cpp.spec (dtor "std::thread::id") as thread_id_dtor_spec with ( - \this this - \pre{o} this |-> thread_idR 1$m o - \post emp). + cpp.spec "MyMutex::MyMutex()" as ctor_spec with + (\exact Reduce (Spec.ctor_spec IR lock_state_gname)). - cpp.spec "std::thread::id::operator=(const std::thread::id&)" - as thread_id_copy_assign_spec with ( - \this this - \arg{other} "" (Vptr other) - \pre{old} this |-> thread_idR 1$m old - \prepost{q o} other |-> thread_idR q o - \post[Vref this] this |-> thread_idR 1$m o). - - cpp.spec "std::thread::id::operator=(std::thread::id&&)" - as thread_id_move_assign_spec with ( - \this this - \arg{other} "" (Vptr other) - \pre{old} this |-> thread_idR 1$m old - \prepost{o} other |-> thread_idR 1$m o - \post[Vref this] this |-> thread_idR 1$m o). - - cpp.spec "std::operator==(std::thread::id, std::thread::id)" - as thread_id_eq_spec with ( - \arg{lhs} "" (Vptr lhs) - \arg{rhs} "" (Vptr rhs) - \prepost{q1 o1} lhs |-> thread_idR q1 o1 - \prepost{q2 o2} rhs |-> thread_idR q2 o2 - \post[Vbool (bool_decide (o1 = o2))] emp). - - cpp.spec "std::this_thread::get_id()" as get_id_spec with ( - \persist{thr} current_thread thr - \post{result}[Vptr result] - result |-> thread_idR 1$m (Some thr)). - - cpp.spec "MyMutex::MyMutex()" as ctor_spec with ( - \this this - \pre{P} ▷P - \require WeaklyObjective P - \post (|={⊤}=> Exists g, - this |-> IR g 1$m P ** LockState.token g.(lock_state_gname) 1%Qp)). - - cpp.spec "MyMutex::~MyMutex()" as dtor_spec with ( - \this this - \pre{g P} this |-> IR g 1$m P ** LockState.token g.(lock_state_gname) 1%Qp - \post P). - - cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( - \this this - \prepost{g q P} this |-> IR g q P - \persist{thr} current_thread thr - \pre LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) - \prepost{q'} GLOBALS q' - (* does not have to be q, but easier if it is *) - \post (P ** - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - LockState.locked g.(lock_state_gname) (Some thr) q)). - - cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( - \this this - \prepost{g q P} this |-> IR g q P - \persist{thr} current_thread thr - \pre ▷P - \pre this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \pre LockState.locked g.(lock_state_gname) (Some thr) q - \post LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)). + cpp.spec "MyMutex::~MyMutex()" as dtor_spec with + (\exact Reduce (Spec.dtor_spec IR lock_state_gname)). + (** Keep the representation fraction in the state, since this implementation + uses the same fraction for [IR] and the locking tokens. *) Definition T : Type := gname * cQp.t * mpred. - - Definition do_lock (this : ptr) (lk : T) (K : mpred) : mpred := - let g := lk.1.1 in - let q := lk.1.2 in - let P := lk.2 in - ∃ thr q', current_thread thr ** - LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) ** - GLOBALS q' ** - (GLOBALS q' ** P ** this |-> locked g thr q -* K). + Abbreviation mutexR := (fun (gq : gname * cQp.t) (_ : cQp.t) P => + IR gq.1 gq.2 P). + (* the raw assertions are used for the internal do_lock() and do_unlock() *) + Abbreviation not_locked_raw := + (fun (_ : ptr) (gq : gname * cQp.t) thr (_ : unit) => + LockState.not_locked gq.1.(lock_state_gname) thr gq.2 gq.1.(cinv_gname)). + Abbreviation locked_raw := + (fun (this : ptr) (gq : gname * cQp.t) thr (_ : unit) => + (this |-> lockedR gq.1 thr gq.2)%I). + + (** Locking additionally preserves the globals used by the spin loop. *) + Abbreviation not_locked := (fun this gq thr q' => + (not_locked_raw this gq thr tt ** GLOBALS q')%I). + Abbreviation locked := (fun this gq thr q' => + (GLOBALS q' ** locked_raw this gq thr tt)%I). + + Abbreviation raw_locked_raw := + (fun (this : ptr) (gq : gname * cQp.t) thr (_ : unit) => + (this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + LockState.locked gq.1.(lock_state_gname) (Some thr) gq.2)%I). + Abbreviation raw_locked := (fun this gq thr q' => + (GLOBALS q' ** raw_locked_raw this gq thr tt)%I). + + cpp.spec "MyMutex::do_lock()" as do_lock_spec with + (\exact Reduce (Spec.lock_spec_alt mutexR not_locked raw_locked)). + + cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with + (\exact Reduce (Spec.unlock_spec_alt mutexR not_locked_raw raw_locked_raw)). + + Definition do_lock := Spec.do_lock not_locked locked. #[global] Arguments do_lock /. - - Definition do_unlock (this : ptr) (lk : T) (K : mpred) : mpred := - let g := lk.1.1 in - let q := lk.1.2 in - let P := lk.2 in - ∃ thr , current_thread thr ** this |-> locked g thr q ** ▷P ** - (LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) -* K). + Definition do_unlock := Spec.do_unlock not_locked_raw locked_raw. #[global] Arguments do_unlock /. #[global] Instance custom_mutex_basic_lockable : @@ -193,20 +197,11 @@ Module custom_mutex. { do_lock := do_lock ; do_unlock := do_unlock }. - cpp.spec "MyMutex::lock()" as lock_spec_alt with ( - \this this - \prepost{g q P} this |-> IR g q P - \persist{thr} current_thread thr - \pre LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) - \prepost{q'} GLOBALS q' - \post P ** this |-> locked g thr q). + cpp.spec "MyMutex::lock()" as lock_spec_alt with + (\exact Reduce (Spec.lock_spec_alt mutexR not_locked locked)). - cpp.spec "MyMutex::unlock()" as unlock_spec_alt with ( - \this this - \prepost{g q P} this |-> IR g q P - \persist{thr} current_thread thr - \pre this |-> locked g thr q ** ▷P - \post LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)). + cpp.spec "MyMutex::unlock()" as unlock_spec_alt with + (\exact Reduce (Spec.unlock_spec_alt mutexR not_locked_raw locked_raw)). cpp.spec "MyMutex::lock()" as lock_spec with (\exact Reduce @@ -216,35 +211,6 @@ Module custom_mutex. (\exact Reduce (unlock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). - Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. - Proof. - iSplit; iApply specify_mono; ework with br_erefl. - lazymatch goal with - | |- environments.envs_entails _ ?Ggoal => - lazymatch Ggoal with - | context[IR ?gqP.1.1 ?gqP.1.2 ?gqP.2] => unify gqP (g, q, P) - end - end. - ework with br_erefl. - Unshelve. all: exact (1$m)%cQp. - Qed. - - Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. - Proof. - iSplit; iApply specify_mono; ework with br_erefl. - lazymatch goal with - | |- environments.envs_entails _ ?Ggoal => - lazymatch Ggoal with - | context[IR ?gqP.1.1 ?gqP.1.2 ?gqP.2] => unify gqP (g, q, P) - end - end. - ework with br_erefl. - Unshelve. all: exact (1$m)%cQp. - Qed. - - cpp.spec "std::this_thread::yield()" as yield_spec with ( - \post emp). - Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). Definition bi_later_exist_F := [FWD] @bi.later_exist. @@ -386,7 +352,7 @@ Module custom_mutex. Qed. Hint Resolve do_load_C : sl_opacity. - Hint Opaque locked : sl_opacity. + Hint Opaque lockedR : sl_opacity. Lemma mymutex_do_lock_proof : verify[source] "MyMutex::do_lock()". Proof using MOD HAS_THREADS. @@ -403,15 +369,17 @@ Module custom_mutex. Lemma mymutex_lock_alt_proof : verify[source] lock_spec_alt. Proof using MOD HAS_THREADS. verify_spec; ego. - rewrite locked.unlock. + rewrite lockedR.unlock. ego. + Unshelve. all: first [exact (1$m)%cQp | exact tt]. Qed. Lemma mymutex_unlock_alt_proof : verify[source] unlock_spec_alt. Proof using MOD HAS_THREADS. verify_spec. - rewrite locked.unlock. + rewrite lockedR.unlock. repeat (go; ework). + Unshelve. all: first [exact (1$m)%cQp | exact tt]. Qed. Lemma mymutex_ctor_proof : verify[source] "MyMutex::MyMutex()". @@ -462,16 +430,17 @@ Module custom_mutex. Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. - rewrite lock_spec_entails_lock_spec_alt. + have -> : lock_spec ⊣⊢ lock_spec_alt. + { apply (Spec.lock_spec_entails_lock_spec_alt mutexR not_locked locked). done. } exact mymutex_lock_alt_proof. Qed. Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. - rewrite unlock_spec_entails_unlock_spec_alt. + have -> : unlock_spec ⊣⊢ unlock_spec_alt. + { apply (Spec.unlock_spec_entails_unlock_spec_alt mutexR not_locked_raw locked_raw). done. } exact mymutex_unlock_alt_proof. Qed. - End with_Σ. End custom_mutex. From 0dc1f8dd023bf2c6f7eb3a54b9e438e67281a8d0 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 02:46:56 +0000 Subject: [PATCH 022/109] better model for my_mutex, all properties described in MUTEX_SETS --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 506 ++++++++++++------- 1 file changed, 334 insertions(+), 172 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 9e299025..bd34f839 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -1,5 +1,6 @@ Require Import iris.algebra.agree. Require Import iris.algebra.frac. +Require Import iris.algebra.functions. Require Import iris.algebra.gmap. Require Import iris.algebra.gset. Require Import iris.algebra.lib.excl_auth. @@ -10,9 +11,6 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Import linearity. -(** The mutex specification depends on the ghost state through these - interfaces. *) - Module Type MUTEX_SETS. Parameter cmraR : cmra. @@ -23,9 +21,65 @@ Module Type MUTEX_SETS. }. #[global] Arguments G {_ _} Σ : assert. - Parameter my_mutexes : forall `{Σ : cpp_logic, !G Σ}, - namespace -> iprop.gname -> thread_idT -> gset iprop.gname -> mpred. - + (** All predicates use the same ghost name [γ], with [th] selecting an + entry. [mutex_set_map γ T] retains the authorities for thread IDs + outside the allocated set [T]. *) + Parameter mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> gset thread_idT -> mpred. + Parameter mutex_set_frag : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> thread_idT -> gset_disj iprop.gname -> mpred. + Parameter mutex_set_auth : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> thread_idT -> gset_disj iprop.gname -> mpred. + + (** [sa] records mutexes seen by this thread; [sf] contains its available + mutex fragments, which move to lock invariants while locks are held. *) + Definition my_mutexes `{Σ : cpp_logic, !G Σ} γ th sa sf : mpred := + mutex_set_auth γ th sa ** mutex_set_frag γ th sf. + + #[global] Declare Instance mutex_set_map_timeless + `{Σ : cpp_logic, !G Σ} γ M : Timeless (mutex_set_map γ M). + #[global] Declare Instance mutex_set_frag_timeless + `{Σ : cpp_logic, !G Σ} γ th s : Timeless (mutex_set_frag γ th s). + #[global] Declare Instance mutex_set_auth_timeless + `{Σ : cpp_logic, !G Σ} γ th s : Timeless (mutex_set_auth γ th s). + #[global] Declare Instance my_mutexes_timeless + `{Σ : cpp_logic, !G Σ} γ th sa sf : Timeless (my_mutexes γ th sa sf). + + #[global] Declare Instance mutex_set_map_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ M : WeaklyObjective (mutex_set_map γ M). + #[global] Declare Instance mutex_set_frag_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ th s : WeaklyObjective (mutex_set_frag γ th s). + #[global] Declare Instance mutex_set_auth_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ th s : WeaklyObjective (mutex_set_auth γ th s). + #[global] Declare Instance my_mutexes_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ th sa sf : WeaklyObjective (my_mutexes γ th sa sf). + + Parameter mutex_set_frag_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th γm, + mutex_set_frag γ th (GSet {[γm]}) ** mutex_set_frag γ th (GSet {[γm]}) |-- False. + Parameter alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, + ⊢ |==> ∃ γ, mutex_set_map γ ∅. + Parameter mutex_sets_alloc_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, + th ∉ T -> + mutex_set_map γ T |-- + (|==> mutex_set_map γ (T ∪ {[th]}) ** + my_mutexes γ th (GSet ∅) (GSet ∅)). + Parameter my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} γ th sa sf γm, + γm ∉ sa -> + my_mutexes γ th (GSet sa) sf |-- + (|==> my_mutexes γ th (GSet (sa ∪ {[γm]})) sf ** + mutex_set_frag γ th (GSet {[γm]})). + + (** Distinct threads can own arbitrary, possibly overlapping mutex sets. + The registry and both authoritative sets are retained as the remainder. *) + Parameter mutex_set_frags_alloc : forall `{Σ : cpp_logic, !G Σ} + (th1 th2 : thread_idT) (s1 s2 : gset iprop.gname), + th1 ≠ th2 -> + ⊢ |==> ∃ γ, + mutex_set_frag γ th1 (GSet s1) ** + mutex_set_frag γ th2 (GSet s2) ** + (mutex_set_map γ {[th1; th2]} ** + mutex_set_auth γ th1 (GSet s1) ** + mutex_set_auth γ th2 (GSet s2)). End MUTEX_SETS. Module Type MUTEX_TOKENS. @@ -52,38 +106,36 @@ Module Type MUTEX_TOKENS. #[global] Declare Instance given_token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). + Parameter token_full token_not_full : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> mpred. + #[global] Declare Instance token_full_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ : WeaklyObjective (token_full γ). + #[global] Declare Instance token_not_full_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ : WeaklyObjective (token_not_full γ). + + Parameter token_full_init : forall `{Σ : cpp_logic, !G Σ} γ, + given_token γ 1 |-- token_full γ. + Parameter acquire : forall `{Σ : cpp_logic, !G Σ} γ q, + token_full γ ** token γ q |-- given_token γ q ** token_not_full γ. + Parameter release : forall `{Σ : cpp_logic, !G Σ} γ q, + token_not_full γ ** given_token γ q |-- token γ q ** token_full γ. + Parameter token_not_full_full_token : forall `{Σ : cpp_logic, !G Σ} γ, + token_not_full γ ** token γ 1 |-- False. + Parameter alloc : forall `{Σ : cpp_logic, !G Σ}, ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. End MUTEX_TOKENS. Module Type MUTEX_STATE. - Declare Module Sets : MUTEX_SETS. - Declare Module Tokens : MUTEX_TOKENS. - Parameter owner_cmraR : cmra. - Parameter gname : Set. + Parameter (pool_name : gname -> iprop.gname). - Class G `{Σ : cpp_logic} := { - #[global] sets_G :: Sets.G Σ; - #[global] tokens_G :: Tokens.G Σ; - #[local] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; - #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; - #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; - }. + Parameter G : forall `{Σ : cpp_logic}, Type. + Existing Class G. #[global] Arguments G {_ _} Σ : assert. - Parameter owner_tid_auth : forall `{Σ : cpp_logic, !G Σ}, - gname -> option thread_idT -> mpred. - Parameter owner_tid_frag : forall `{Σ : cpp_logic, !G Σ}, - gname -> option thread_idT -> mpred. - - #[global] Declare Instance owner_tid_auth_timeless - `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_tid_auth γ o_thr). - #[global] Declare Instance owner_tid_frag_timeless - `{Σ : cpp_logic, !G Σ} γ o_thr : Timeless (owner_tid_frag γ o_thr). - #[global] Declare Instance owner_tid_frag_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). - + (** Client resources. [not_locked] permits this thread to attempt acquisition; + [locked] records ownership after successful acquisition. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> Qp -> mpred. Parameter not_locked : forall `{Σ : cpp_logic, !G Σ}, @@ -91,6 +143,15 @@ Module Type MUTEX_STATE. Parameter locked : forall `{Σ : cpp_logic, !G Σ}, gname -> option thread_idT -> Qp -> mpred. + (** The ghost resources kept inside the mutex invariant. The boolean agrees + with the physical lock bit; the invariant name connects acquisitions to + the same mutex. Its representation is private to the implementation. *) + Parameter state : forall `{Σ : cpp_logic, !G Σ}, + gname -> iprop.gname -> bool -> mpred. + #[global] Declare Instance state_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ inv_gname b : + WeaklyObjective (state γ inv_gname b). + #[global] Declare Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). #[global] Declare Instance token_timeless @@ -99,13 +160,33 @@ Module Type MUTEX_STATE. `{Σ : cpp_logic, !G Σ} γ th q : Timeless (locked γ th q). #[global] Declare Instance locked_exclusive `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => locked γ th q). -End MUTEX_STATE. -(** ** Exact per-thread mutex sets *) + (** Each mutex uses the caller's shared mutex-set pool. *) + Parameter alloc : forall `{Σ : cpp_logic, !G Σ} (γpool : iprop.gname) inv_gname, + ⊢ |==> ∃ γ, [| pool_name γ = γpool |] ** + token γ 1 ** state γ inv_gname false. + + (** Successful acquisition and release exchange client resources with the + invariant. Failed acquisition leaves both resources unchanged. *) + Parameter do_lock : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, + state γ inv_gname false ** not_locked γ th q inv_gname |-- + (|==> state γ inv_gname true ** locked γ (Some th) q). + Parameter do_unlock : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, + state γ inv_gname true ** locked γ (Some th) q |-- + (|==> state γ inv_gname false ** not_locked γ th q inv_gname). + + (** Ownership rules out an unlocked physical state. Full destruction + permission rules out a locked physical state. *) + Parameter unlocked_locked : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, + state γ inv_gname false ** locked γ th q |-- False. + Parameter locked_full_token : forall `{Σ : cpp_logic, !G Σ} γ inv_gname, + state γ inv_gname true ** token γ 1 |-- False. +End MUTEX_STATE. -Module MutexSets. +Module MutexSets : MUTEX_SETS. + Canonical Structure threadR := authUR (gset_disjR iprop.gname). Canonical Structure cmraR : cmra := - gmap_viewR thread_idT (agreeR (leibnizO (gset iprop.gname))). + discrete_funUR (fun _ : thread_idT => threadR). Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; @@ -114,160 +195,160 @@ Module MutexSets. }. #[global] Arguments G {_ _} Σ : assert. - Definition mutex_sets_view - (M : gmap thread_idT (gset iprop.gname)) : - gmap thread_idT (agree (leibnizO (gset iprop.gname))) := - (λ X : gset iprop.gname, to_agree X) <$> M. - - sl.lock - Definition mutex_sets_auth `{Σ : cpp_logic, !G Σ} - (γpool : iprop.gname) - (M : gmap thread_idT (gset iprop.gname)) : mpred := - own γpool (gmap_view_auth (DfracOwn 1) (mutex_sets_view M)). - - sl.lock - Definition mutex_sets_frag `{Σ : cpp_logic, !G Σ} - (γpool : iprop.gname) (th : thread_idT) - (M : gset iprop.gname) : mpred := - own γpool - (gmap_view_frag - (V := agreeR (leibnizO (gset iprop.gname))) - th (DfracOwn 1) (to_agree M)). - - sl.lock - Definition my_mutexes_inv `{Σ : cpp_logic, !G Σ} - (γpool : iprop.gname) : mpred := - ∃ M : gmap thread_idT (gset iprop.gname), mutex_sets_auth γpool M. - - Definition my_mutexes `{Σ : cpp_logic, !G Σ} - (N : namespace) (γpool : iprop.gname) (th : thread_idT) - (M : gset iprop.gname) : mpred := - inv N (my_mutexes_inv γpool) ** mutex_sets_frag γpool th M. - - #[only(timeless)] derive mutex_sets_auth. - #[only(timeless)] derive mutex_sets_frag. - #[only(timeless)] derive my_mutexes_inv. - - #[global] Instance my_mutexes_inv_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γpool : - WeaklyObjective (my_mutexes_inv γpool). - Proof. - rewrite my_mutexes_inv.unlock mutex_sets_auth.unlock. apply _. - Qed. - - #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} N γ th M : - WeaklyObjective (my_mutexes N γ th M). - Proof. rewrite /my_mutexes mutex_sets_frag.unlock. apply _. Qed. - #[global] Hint Opaque my_mutexes : sl_opacity typeclass_instances. + Definition mutex_set_auth `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (th : thread_idT) (s : gset_disj iprop.gname) : mpred := + own γ (discrete_fun_singleton th (● s) : cmraR). + + Definition mutex_set_frag `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (th : thread_idT) (s : gset_disj iprop.gname) : mpred := + own γ (discrete_fun_singleton th (◯ s) : cmraR). + + Definition my_mutexes `{Σ : cpp_logic, !G Σ} γ th sa sf : mpred := + mutex_set_auth γ th sa ** mutex_set_frag γ th sf. + + Definition reserve (M : gset thread_idT) : cmraR := + fun th => if decide (th ∈ M) then ε else ● (GSet ∅). + + Definition mutex_set_map `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (M : gset thread_idT) : mpred := + own γ (reserve M). + + #[global] Instance mutex_set_map_timeless `{Σ : cpp_logic, !G Σ} γ M : + Timeless (mutex_set_map γ M). + Proof. rewrite /mutex_set_map. apply _. Qed. + #[global] Instance mutex_set_frag_timeless `{Σ : cpp_logic, !G Σ} γ th s : + Timeless (mutex_set_frag γ th s). + Proof. rewrite /mutex_set_frag. apply _. Qed. + #[global] Instance mutex_set_auth_timeless `{Σ : cpp_logic, !G Σ} γ th s : + Timeless (mutex_set_auth γ th s). + Proof. rewrite /mutex_set_auth. apply _. Qed. + #[global] Instance my_mutexes_timeless `{Σ : cpp_logic, !G Σ} γ th sa sf : + Timeless (my_mutexes γ th sa sf). + Proof. rewrite /my_mutexes. apply _. Qed. + + #[global] Instance mutex_set_map_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ M : + WeaklyObjective (mutex_set_map γ M). + Proof. rewrite /mutex_set_map. apply _. Qed. + #[global] Instance mutex_set_frag_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th s : + WeaklyObjective (mutex_set_frag γ th s). + Proof. rewrite /mutex_set_frag. apply _. Qed. + #[global] Instance mutex_set_auth_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th s : + WeaklyObjective (mutex_set_auth γ th s). + Proof. rewrite /mutex_set_auth. apply _. Qed. + #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th sa sf : + WeaklyObjective (my_mutexes γ th sa sf). + Proof. rewrite /my_mutexes. apply _. Qed. Section theory. Context `{Σ : cpp_logic, !G Σ}. - Lemma alloc_pool N : - ⊢ |={⊤}=> ∃ γpool, inv N (my_mutexes_inv γpool). + Lemma mutex_set_frag_exclusive γ th γm : + mutex_set_frag γ th (GSet {[γm]}) ** + mutex_set_frag γ th (GSet {[γm]}) |-- False. Proof. - iMod (own_alloc - (gmap_view_auth (V := agreeR (leibnizO (gset iprop.gname))) - (DfracOwn 1) (mutex_sets_view ∅))) as (γpool) "HA". - { apply gmap_view_auth_valid. } - iMod (inv_alloc N _ (my_mutexes_inv γpool) with "[HA]") as "#HI". - { iNext. rewrite my_mutexes_inv.unlock mutex_sets_auth.unlock. - iExists ∅. iExact "HA". } - iModIntro. iExists γpool. iExact "HI". - Qed. - - Lemma mutex_sets_frag_exclusive γpool th M1 M2 : - mutex_sets_frag γpool th M1 ** mutex_sets_frag γpool th M2 |-- False. - Proof. - rewrite mutex_sets_frag.unlock. + rewrite /mutex_set_frag. iIntros "[H1 H2]". iDestruct (own_valid_2 with "H1 H2") as %Hvalid. - apply gmap_view_frag_op_valid in Hvalid as [Hfrac _]. - rewrite dfrac_op_own dfrac_valid_own in Hfrac. - exfalso. exact (Qp.not_add_le_l 1 1 Hfrac). + iPureIntro. + specialize (Hvalid th). + rewrite discrete_fun_lookup_op !discrete_fun_lookup_singleton in Hvalid. + rewrite -auth_frag_op auth_frag_valid gset_disj_valid_op in Hvalid. + set_solver. Qed. - Lemma mutex_sets_update γpool M th S S' : - mutex_sets_auth γpool M ** mutex_sets_frag γpool th S |-- - (|==> mutex_sets_auth γpool (<[th := S']> M) ** - mutex_sets_frag γpool th S'). + Lemma alloc_mutex_set_map : + ⊢ |==> ∃ γ, mutex_set_map γ ∅. Proof. - rewrite mutex_sets_auth.unlock mutex_sets_frag.unlock - /mutex_sets_view fmap_insert. - iIntros "[HA HF]". - iMod (own_update_2 with "HA HF") as "[HA HF]". - { apply (gmap_view_replace - (V := agreeR (leibnizO (gset iprop.gname))) - (mutex_sets_view M) th (to_agree S) (to_agree S')). done. } - iModIntro. iFrame. + iMod (own_alloc (reserve ∅)) as (γ) "Hmap". + { intros th. rewrite /reserve. + apply auth_auth_valid. done. } + iModIntro. iExists γ. iExact "Hmap". Qed. - Lemma mutex_sets_alloc_thread γpool M th : - M !! th = None -> - mutex_sets_auth γpool M |-- - (|==> mutex_sets_auth γpool (<[th := ∅]> M) ** - mutex_sets_frag γpool th ∅). + Lemma my_mutexes_alloc_mutex_name γ th sa sf γm : + γm ∉ sa -> + my_mutexes γ th (GSet sa) sf |-- + (|==> my_mutexes γ th (GSet (sa ∪ {[γm]})) sf ** + mutex_set_frag γ th (GSet {[γm]})). Proof. - rewrite mutex_sets_auth.unlock mutex_sets_frag.unlock - /mutex_sets_view fmap_insert. - iIntros (Hfresh) "HA". - iMod (own_update with "HA") as "[HA HF]". - { apply (gmap_view_alloc - (V := agreeR (leibnizO (gset iprop.gname))) - (mutex_sets_view M) th (DfracOwn 1) (to_agree ∅)). - - rewrite lookup_fmap Hfresh. done. - - done. - - done. } + rewrite /my_mutexes /mutex_set_auth /mutex_set_frag. + iIntros (Hfresh) "[HA HF]". + iMod (own_update γ _ + ((discrete_fun_singleton th (● GSet (sa ∪ {[γm]})) ⋅ + discrete_fun_singleton th (◯ GSet {[γm]})) : cmraR) + with "HA") as "[HA Hnew]". + { rewrite discrete_fun_singleton_op. + apply discrete_fun_singleton_update. + rewrite (comm_L union). + apply auth_update_alloc. + apply gset_disj_alloc_empty_local_update. set_solver. } iModIntro. iFrame. Qed. - Lemma my_mutexes_exclusive N1 N2 γpool th M1 M2 : - my_mutexes N1 γpool th M1 ** my_mutexes N2 γpool th M2 |-- False. + Lemma mutex_sets_alloc_thread_with_set + γ (T : gset thread_idT) th (s : gset iprop.gname) : + th ∉ T -> + mutex_set_map γ T |-- + (|==> mutex_set_map γ (T ∪ {[th]}) ** + my_mutexes γ th (GSet s) (GSet s)). Proof. - rewrite /my_mutexes. - iIntros "[[_ H1] [_ H2]]". - iApply (mutex_sets_frag_exclusive with "[$H1 $H2]"). + rewrite /mutex_set_map /my_mutexes /mutex_set_auth /mutex_set_frag. + iIntros (Hfresh) "Hmap". + iMod (own_update γ _ (reserve (T ∪ {[th]}) ⋅ + (discrete_fun_singleton th (● GSet s) ⋅ discrete_fun_singleton th (◯ GSet s))) + with "Hmap") as "[Hmap [HA HF]]". + { apply discrete_fun_update. intros th'. + rewrite !discrete_fun_lookup_op. + destruct (decide (th = th')) as [<-|Hne]. + - rewrite !discrete_fun_lookup_singleton /reserve. + case_decide; first contradiction. + case_decide; last set_solver. + rewrite left_id. + apply auth_update_alloc. + rewrite -{1}(right_id_L ∅ union s). + apply gset_disj_alloc_empty_local_update. set_solver. + - rewrite !discrete_fun_lookup_singleton_ne; try done. + rewrite left_id right_id /reserve. + destruct (decide (th' ∈ T)). + + rewrite !decide_True; try set_solver. + + rewrite !decide_False; try set_solver. + } + iModIntro. iFrame. Qed. - Lemma my_mutexes_alloc N th : - ⊢ |={⊤}=> ∃ γpool, my_mutexes N γpool th ∅. + Lemma mutex_sets_alloc_thread γ T th : + th ∉ T -> + mutex_set_map γ T |-- + (|==> mutex_set_map γ (T ∪ {[th]}) ** + my_mutexes γ th (GSet ∅) (GSet ∅)). + Proof. apply mutex_sets_alloc_thread_with_set. Qed. + + Lemma mutex_set_frags_alloc (th1 th2 : thread_idT) (s1 s2 : gset iprop.gname) : + th1 ≠ th2 -> + ⊢ |==> ∃ γ, + mutex_set_frag γ th1 (GSet s1) ** + mutex_set_frag γ th2 (GSet s2) ** + (mutex_set_map γ {[th1; th2]} ** + mutex_set_auth γ th1 (GSet s1) ** + mutex_set_auth γ th2 (GSet s2)). Proof. - iMod (own_alloc - (gmap_view_auth - (V := agreeR (leibnizO (gset iprop.gname))) - (DfracOwn 1) (mutex_sets_view ∅))) as (γpool) "HA". - { apply gmap_view_auth_valid. } - iAssert (mutex_sets_auth γpool ∅) with "[HA]" as "Hauth". - { rewrite mutex_sets_auth.unlock. iExact "HA". } - have Hfresh : - (∅ : gmap thread_idT (gset iprop.gname)) !! th = None by done. - iMod (mutex_sets_alloc_thread γpool ∅ th Hfresh with "Hauth") - as "[HA HF]". - iMod (inv_alloc N _ (my_mutexes_inv γpool) with "[HA]") as "#Hinv". - { iNext. rewrite my_mutexes_inv.unlock. - iExists ({[th := ∅]} : gmap thread_idT (gset iprop.gname)). - iFrame. } - iModIntro. iExists γpool. rewrite /my_mutexes. iFrame. - Unshelve. all: try done. + iIntros (Hneq). + iMod alloc_mutex_set_map as (γ) "Hmap". + iMod (mutex_sets_alloc_thread_with_set γ ∅ th1 s1 ltac:(set_solver) + with "Hmap") as "[Hmap Ht1]". + iEval (rewrite left_id_L) in "Hmap". + iMod (mutex_sets_alloc_thread_with_set γ {[th1]} th2 s2 ltac:(set_solver) + with "Hmap") as "[Hmap Ht2]". + iDestruct "Ht1" as "[Ha1 Hf1]". + iDestruct "Ht2" as "[Ha2 Hf2]". + iModIntro. iExists γ. iFrame. Qed. - Lemma my_mutexes_insert N γpool th M g : - my_mutexes N γpool th M |-- - (|={⊤}=> my_mutexes N γpool th (M ∪ {[g]})). - Proof. - rewrite /my_mutexes. - iIntros "[#Hinv HF]". - iInv N as "Hpool" "Hclose". - rewrite my_mutexes_inv.unlock. - iDestruct "Hpool" as (A) ">HA". - iMod (mutex_sets_update γpool A th M (M ∪ {[g]}) - with "[$HA $HF]") as "[HA HF]". - iMod ("Hclose" with "[HA]") as "_". - { iNext. iExists _. iFrame. } - iModIntro. iFrame "Hinv HF". - Qed. End theory. + + #[global] Hint Opaque mutex_set_map mutex_set_auth mutex_set_frag + my_mutexes : sl_opacity typeclass_instances. End MutexSets. (** ** The fractional token/given-token pair *) @@ -338,6 +419,9 @@ Module MutexTokens. #[local] Existing Instance mpred_BiAffine. + Lemma token_full_init γ : given_token γ 1 |-- token_full γ. + Proof. rewrite /token_full. iIntros "T". iLeft. iExact "T". Qed. + Lemma token_valid γ q : token γ q |-- [| (q ≤ 1)%Qp |]. Proof. rewrite /token. iIntros "H". @@ -454,21 +538,24 @@ Module MakeMutexState excl_authR (optionO thread_idTO). Record mutex_gname : Set := MkGname { - pool_namespace : namespace; pool_gname : iprop.gname; token_gname : iprop.gname; owner_gname : iprop.gname; }. Definition gname : Set := mutex_gname. + Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). - Class G `{Σ : cpp_logic} := { + Class stateG `{Σ : cpp_logic} := { #[global] sets_G :: Sets.G Σ; #[global] tokens_G :: Tokens.G Σ; #[global] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; #[global] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; #[global] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; }. + Definition G := @stateG. + Existing Class G. #[global] Arguments G {_ _} Σ : assert. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} (γ : gname) (o_thr : option thread_idT) : mpred := @@ -524,10 +611,9 @@ Module MakeMutexState Tokens.token γ.(token_gname) q. Definition not_locked `{Σ : cpp_logic, !G Σ} - (γ : gname) (th : thread_idT) (q : Qp) + (γ : gname) (th : thread_idT) (q : Qp) (inv_gname : iprop.gname) : mpred := - Sets.my_mutexes - γ.(pool_namespace) γ.(pool_gname) th {[inv_gname]} ** + Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** Tokens.token γ.(token_gname) q. Definition locked `{Σ : cpp_logic, !G Σ} @@ -537,9 +623,7 @@ Module MakeMutexState Lemma not_locked_eq `{Σ : cpp_logic, !G Σ} γ th q inv_gname : not_locked γ th q inv_gname ⊣⊢ - Sets.my_mutexes - γ.(pool_namespace) γ.(pool_gname) th - {[inv_gname]} ** + Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** Tokens.token γ.(token_gname) q. Proof. done. Qed. @@ -568,7 +652,85 @@ Module MakeMutexState apply _. Qed. - #[global] Hint Opaque token not_locked locked : sl_opacity typeclass_instances. + (** While held, the invariant owns this thread's singleton mutex fragment + and the token balance. The thread retains its mutex-set authority. + When free, both halves of the previous owner remain in the invariant. *) + Definition state `{Σ : cpp_logic, !G Σ} + (γ : gname) (inv_gname : iprop.gname) (b : bool) : mpred := + (if b then + ∃ th, owner_tid_auth γ (Some th) ** + Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** + Tokens.token_not_full γ.(token_gname) + else + ∃ owner, owner_tid_auth γ owner ** owner_tid_frag γ owner ** + Tokens.token_full γ.(token_gname))%I. + + #[global] Instance state_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ inv_gname b : + WeaklyObjective (state γ inv_gname b). + Proof. rewrite /state. destruct b; apply _. Qed. + + Section state_laws. + Context `{Σ : cpp_logic, !G Σ}. + + Lemma alloc (γpool : iprop.gname) inv_gname : + ⊢ |==> ∃ γ, [| pool_name γ = γpool |] ** + token γ 1 ** state γ inv_gname false. + Proof. + iMod Tokens.alloc as (gt) "[T GT]". + iMod (own_alloc ((●E None ⋅ ◯E None) : owner_cmraR)) as (go) "O". + { apply excl_auth_valid. } + iDestruct (own_op with "O") as "[OA OF]". + iModIntro. iExists (MkGname γpool gt go). + iSplit; first done. + rewrite /token /state /=. iFrame "T". iExists None. + rewrite /owner_tid_auth /owner_tid_frag /=. iFrame "OA OF". + iApply Tokens.token_full_init. iExact "GT". + Qed. + + Lemma do_lock γ inv_gname th q : + state γ inv_gname false ** not_locked γ th q inv_gname |-- + (|==> state γ inv_gname true ** locked γ (Some th) q). + Proof. + rewrite /state /not_locked /locked. + iIntros "[State [Sets T]]". + iDestruct "State" as (owner) "(OA & OF & Balance)". + iDestruct (Tokens.acquire with "[$Balance $T]") as "[GT Balance]". + iMod (owner_update _ _ _ (Some th) with "[$OA $OF]") as "[OA OF]". + iModIntro. iFrame "GT OF". iExists th. iFrame. + Qed. + + Lemma do_unlock γ inv_gname th q : + state γ inv_gname true ** locked γ (Some th) q |-- + (|==> state γ inv_gname false ** not_locked γ th q inv_gname). + Proof. + rewrite /state /locked /not_locked. + iIntros "[State [GT OF]]". + iDestruct "State" as (owner) "(OA & Sets & Balance)". + iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. + injection Heq as ->. + iDestruct (Tokens.release with "[$Balance $GT]") as "[T Balance]". + iModIntro. iFrame "Sets T". iExists (Some th). iFrame. + Qed. + + Lemma unlocked_locked γ inv_gname th q : + state γ inv_gname false ** locked γ th q |-- False. + Proof. + rewrite /state /locked. iIntros "[State [_ OF]]". + iDestruct "State" as (owner) "(_ & OF0 & _)". + iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. + Qed. + + Lemma locked_full_token γ inv_gname : + state γ inv_gname true ** token γ 1 |-- False. + Proof. + rewrite /state /token. iIntros "[State T]". + iDestruct "State" as (th) "(_ & _ & Balance)". + iApply (Tokens.token_not_full_full_token with "[$Balance $T]"). + Qed. + End state_laws. + + #[global] Hint Opaque token not_locked locked state : sl_opacity typeclass_instances. End MakeMutexState. From bf5e89345f6c14592f31c0219dc0bc081dbf5aa8 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 19:06:03 +0000 Subject: [PATCH 023/109] mutex ghost state module instantiation is now associated with implementation proof --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 322 +++++---------- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 93 ++--- .../test/mutex/custom_mutex_hpp_proof.v | 383 ++++++++++++------ 3 files changed, 398 insertions(+), 400 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index bd34f839..cf952763 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -11,6 +11,18 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Import linearity. +(** Ghost state and laws for mutex specs and proofs. *) + +(** MUTEX_SETS has 2 parts: `mutex_set_map g (T:get thread_idT)` for registering + new threads and allocating their `my_mutexes g th` with + `mutex_sets_alloc_thread`, which is a pair of `mutex_set_auth` and + `mutex_set_frag` of gnames. + The thread keeps auth and trades a fraction `mutex_set_frag {[ginv]}` to + `inv ginv P` for resources so it only gets resources once from the invariant + until it gives resources back (`mutex_set_frag_exclusive`). + If `ginv` is not allcated yet, it can be allocated with + `my_mutexes_alloc_mutex_name`. +*) Module Type MUTEX_SETS. Parameter cmraR : cmra. @@ -21,9 +33,6 @@ Module Type MUTEX_SETS. }. #[global] Arguments G {_ _} Σ : assert. - (** All predicates use the same ghost name [γ], with [th] selecting an - entry. [mutex_set_map γ T] retains the authorities for thread IDs - outside the allocated set [T]. *) Parameter mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, iprop.gname -> gset thread_idT -> mpred. Parameter mutex_set_frag : forall `{Σ : cpp_logic, !G Σ}, @@ -69,8 +78,10 @@ Module Type MUTEX_SETS. (|==> my_mutexes γ th (GSet (sa ∪ {[γm]})) sf ** mutex_set_frag γ th (GSet {[γm]})). - (** Distinct threads can own arbitrary, possibly overlapping mutex sets. - The registry and both authoritative sets are retained as the remainder. *) + (** This is more of a sanity check. Maybe there are better rules that should + be included in the module instead of this. + Distinct threads can allocate, possibly overlapping gname sets. + Frags are given to the lock invariants while auth are held by threads. *) Parameter mutex_set_frags_alloc : forall `{Σ : cpp_logic, !G Σ} (th1 th2 : thread_idT) (s1 s2 : gset iprop.gname), th1 ≠ th2 -> @@ -126,63 +137,73 @@ Module Type MUTEX_TOKENS. ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. End MUTEX_TOKENS. +(** Authoritative and exclusive fragment ownership of an optional thread ID. *) +Module Type OWNER_TID. + Parameter cmraR : cmra. + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Parameter owner_tid_auth owner_tid_frag : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> option thread_idT -> mpred. + + #[global] Declare Instance owner_tid_auth_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_auth γ o). + #[global] Declare Instance owner_tid_frag_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_frag γ o). + #[global] Declare Instance owner_tid_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). + #[global] Declare Instance owner_tid_auth_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_auth γ o). + #[global] Declare Instance owner_tid_frag_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_frag γ o). + #[global] Declare Instance owner_agree + `{Σ : cpp_logic, !G Σ} γ o1 o2 : + Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). + + Parameter alloc : forall `{Σ : cpp_logic, !G Σ} o, + ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. + Parameter owner_update : forall `{Σ : cpp_logic, !G Σ} γ oa ofrag o', + owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- + (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). +End OWNER_TID. + +(** A MUTEX_STATE says a mutex spec is parametrized by some `token`, `not_locked` + and `locked`. The exact model depends on the implementation. *) Module Type MUTEX_STATE. Parameter gname : Set. - Parameter (pool_name : gname -> iprop.gname). + Parameter Q : Type. + (* FIXME do we need these? *) + Parameter pool_name inv_name : gname -> iprop.gname. Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. #[global] Arguments G {_ _} Σ : assert. - (** Client resources. [not_locked] permits this thread to attempt acquisition; - [locked] records ownership after successful acquisition. *) + (** [Q] describes the permissions transferred by lock and unlock. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> Qp -> mpred. - Parameter not_locked : forall `{Σ : cpp_logic, !G Σ}, - gname -> thread_idT -> Qp -> iprop.gname -> mpred. - Parameter locked : forall `{Σ : cpp_logic, !G Σ}, - gname -> option thread_idT -> Qp -> mpred. - - (** The ghost resources kept inside the mutex invariant. The boolean agrees - with the physical lock bit; the invariant name connects acquisitions to - the same mutex. Its representation is private to the implementation. *) - Parameter state : forall `{Σ : cpp_logic, !G Σ}, - gname -> iprop.gname -> bool -> mpred. - #[global] Declare Instance state_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ inv_gname b : - WeaklyObjective (state γ inv_gname b). + Parameter not_locked locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + ptr -> gname -> thread_idT -> Q -> mpred. #[global] Declare Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). #[global] Declare Instance locked_timeless - `{Σ : cpp_logic, !G Σ} γ th q : Timeless (locked γ th q). + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + Timeless (locked this γ th q). #[global] Declare Instance locked_exclusive - `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => locked γ th q). - - (** Each mutex uses the caller's shared mutex-set pool. *) - Parameter alloc : forall `{Σ : cpp_logic, !G Σ} (γpool : iprop.gname) inv_gname, - ⊢ |==> ∃ γ, [| pool_name γ = γpool |] ** - token γ 1 ** state γ inv_gname false. - - (** Successful acquisition and release exchange client resources with the - invariant. Failed acquisition leaves both resources unchanged. *) - Parameter do_lock : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, - state γ inv_gname false ** not_locked γ th q inv_gname |-- - (|==> state γ inv_gname true ** locked γ (Some th) q). - Parameter do_unlock : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, - state γ inv_gname true ** locked γ (Some th) q |-- - (|==> state γ inv_gname false ** not_locked γ th q inv_gname). - - (** Ownership rules out an unlocked physical state. Full destruction - permission rules out a locked physical state. *) - Parameter unlocked_locked : forall `{Σ : cpp_logic, !G Σ} γ inv_gname th q, - state γ inv_gname false ** locked γ th q |-- False. - Parameter locked_full_token : forall `{Σ : cpp_logic, !G Σ} γ inv_gname, - state γ inv_gname true ** token γ 1 |-- False. + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ q : + Exclusive1 (fun th => locked this γ th q). End MUTEX_STATE. +(* Proofs that the ghost state modules are inhabited. *) + Module MutexSets : MUTEX_SETS. Canonical Structure threadR := authUR (gset_disjR iprop.gname). Canonical Structure cmraR : cmra := @@ -353,7 +374,7 @@ End MutexSets. (** ** The fractional token/given-token pair *) -Module MutexTokens. +Module MutexTokens <: MUTEX_TOKENS. Canonical Structure cmraR : cmra := prodUR (optionUR fracR) (optionUR fracR). @@ -521,70 +542,45 @@ Module MutexTokens. End theory. End MutexTokens. -(** The concrete state abstracts over mutex sets and tokens, and implements - optional owner state directly. *) -Module MakeMutexState - (Sets0 : MUTEX_SETS) - (Tokens0 : MUTEX_TOKENS) <: MUTEX_STATE. - Module Sets := Sets0. - Module Tokens := Tokens0. - - #[local] Existing Instance Tokens.token_fractional. - #[local] Existing Instance Tokens.given_token_fractional. - #[local] Existing Instance Tokens.token_timeless. - #[local] Existing Instance Tokens.given_token_timeless. - - Canonical Structure owner_cmraR : cmra := - excl_authR (optionO thread_idTO). - - Record mutex_gname : Set := MkGname { - pool_gname : iprop.gname; - token_gname : iprop.gname; - owner_gname : iprop.gname; - }. - Definition gname : Set := mutex_gname. - Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). - - Class stateG `{Σ : cpp_logic} := { - #[global] sets_G :: Sets.G Σ; - #[global] tokens_G :: Tokens.G Σ; - #[global] has_owner :: HasOwn (iPropI _Σ) owner_cmraR; - #[global] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; - #[global] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; +(** The exclusive-authoritative implementation of [OWNER_TID]. *) +Module OwnerTid : OWNER_TID. + Canonical Structure cmraR : cmra := excl_authR (optionO thread_idTO). + + Class G `{Σ : cpp_logic} := { + #[local] has_own :: HasOwn (iPropI _Σ) cmraR; + #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; + #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; }. - Definition G := @stateG. - Existing Class G. #[global] Arguments G {_ _} Σ : assert. - #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} - (γ : gname) (o_thr : option thread_idT) : mpred := - own γ.(owner_gname) ((●E o_thr) : owner_cmraR). - + (γ : iprop.gname) (o : option thread_idT) : mpred := + own γ ((●E o) : cmraR). Definition owner_tid_frag `{Σ : cpp_logic, !G Σ} - (γ : gname) (o_thr : option thread_idT) : mpred := - own γ.(owner_gname) ((◯E o_thr) : owner_cmraR). + (γ : iprop.gname) (o : option thread_idT) : mpred := + own γ ((◯E o) : cmraR). - #[global] Hint Opaque owner_tid_auth owner_tid_frag : sl_opacity typeclass_instances. - - #[only(timeless)] derive owner_tid_auth. - #[only(timeless)] derive owner_tid_frag. + #[global] Instance owner_tid_auth_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_auth γ o). + Proof. rewrite /owner_tid_auth. apply _. Qed. + #[global] Instance owner_tid_frag_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_frag γ o). + Proof. rewrite /owner_tid_frag. apply _. Qed. #[global] Instance owner_tid_frag_exclusive `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). Proof. - intros o_thr1 o_thr2. rewrite /owner_tid_frag. + intros o1 o2. rewrite /owner_tid_frag. iIntros "H1 H2". iDestruct (own_valid_2 with "H1 H2") as %Hvalid. move: Hvalid. rewrite excl_auth_frag_op_valid. done. Qed. - #[global] Instance owner_tid_auth_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ o_thr : - WeaklyObjective (owner_tid_auth γ o_thr). + #[global] Instance owner_tid_auth_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_auth γ o). Proof. rewrite /owner_tid_auth. apply _. Qed. - - #[global] Instance owner_tid_frag_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ o_thr : - WeaklyObjective (owner_tid_frag γ o_thr). + #[global] Instance owner_tid_frag_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_frag γ o). Proof. rewrite /owner_tid_frag. apply _. Qed. #[global] Instance owner_agree `{Σ : cpp_logic, !G Σ} γ o1 o2 : @@ -596,6 +592,15 @@ Module MakeMutexState iPureIntro. apply leibniz_equiv, excl_auth_agree, HV. Qed. + Lemma alloc `{Σ : cpp_logic, !G Σ} o : + ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. + Proof. + iMod (own_alloc ((●E o ⋅ ◯E o) : cmraR)) as (γ) "H". + { apply excl_auth_valid. } + iModIntro. iExists γ. + rewrite /owner_tid_auth /owner_tid_frag -own_op. iExact "H". + Qed. + Lemma owner_update `{Σ : cpp_logic, !G Σ} γ oa ofrag o' : owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). @@ -606,132 +611,5 @@ Module MakeMutexState done. Qed. - Definition token `{Σ : cpp_logic, !G Σ} - (γ : gname) (q : Qp) : mpred := - Tokens.token γ.(token_gname) q. - - Definition not_locked `{Σ : cpp_logic, !G Σ} - (γ : gname) (th : thread_idT) (q : Qp) - (inv_gname : iprop.gname) : mpred := - Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** - Tokens.token γ.(token_gname) q. - - Definition locked `{Σ : cpp_logic, !G Σ} - (γ : gname) (o_thr : option thread_idT) (q : Qp) : mpred := - Tokens.given_token γ.(token_gname) q ** - owner_tid_frag γ o_thr. - - Lemma not_locked_eq `{Σ : cpp_logic, !G Σ} γ th q inv_gname : - not_locked γ th q inv_gname ⊣⊢ - Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** - Tokens.token γ.(token_gname) q. - Proof. done. Qed. - - Lemma locked_eq `{Σ : cpp_logic, !G Σ} γ o_thr q : - locked γ o_thr q ⊣⊢ - Tokens.given_token γ.(token_gname) q ** - owner_tid_frag γ o_thr. - Proof. done. Qed. - - #[global] Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). - Proof. rewrite /token. apply Tokens.token_fractional. Qed. - - #[global] Instance token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). - Proof. rewrite /token. apply _. Qed. - - #[global] Instance locked_timeless - `{Σ : cpp_logic, !G Σ} γ th q : Timeless (locked γ th q). - Proof. rewrite /locked. apply _. Qed. - - #[global] Instance locked_exclusive - `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => locked γ th q). - Proof. - intros th1 th2. rewrite /locked. - apply _. - Qed. - - (** While held, the invariant owns this thread's singleton mutex fragment - and the token balance. The thread retains its mutex-set authority. - When free, both halves of the previous owner remain in the invariant. *) - Definition state `{Σ : cpp_logic, !G Σ} - (γ : gname) (inv_gname : iprop.gname) (b : bool) : mpred := - (if b then - ∃ th, owner_tid_auth γ (Some th) ** - Sets.mutex_set_frag γ.(pool_gname) th (GSet {[inv_gname]}) ** - Tokens.token_not_full γ.(token_gname) - else - ∃ owner, owner_tid_auth γ owner ** owner_tid_frag γ owner ** - Tokens.token_full γ.(token_gname))%I. - - #[global] Instance state_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ inv_gname b : - WeaklyObjective (state γ inv_gname b). - Proof. rewrite /state. destruct b; apply _. Qed. - - Section state_laws. - Context `{Σ : cpp_logic, !G Σ}. - - Lemma alloc (γpool : iprop.gname) inv_gname : - ⊢ |==> ∃ γ, [| pool_name γ = γpool |] ** - token γ 1 ** state γ inv_gname false. - Proof. - iMod Tokens.alloc as (gt) "[T GT]". - iMod (own_alloc ((●E None ⋅ ◯E None) : owner_cmraR)) as (go) "O". - { apply excl_auth_valid. } - iDestruct (own_op with "O") as "[OA OF]". - iModIntro. iExists (MkGname γpool gt go). - iSplit; first done. - rewrite /token /state /=. iFrame "T". iExists None. - rewrite /owner_tid_auth /owner_tid_frag /=. iFrame "OA OF". - iApply Tokens.token_full_init. iExact "GT". - Qed. - - Lemma do_lock γ inv_gname th q : - state γ inv_gname false ** not_locked γ th q inv_gname |-- - (|==> state γ inv_gname true ** locked γ (Some th) q). - Proof. - rewrite /state /not_locked /locked. - iIntros "[State [Sets T]]". - iDestruct "State" as (owner) "(OA & OF & Balance)". - iDestruct (Tokens.acquire with "[$Balance $T]") as "[GT Balance]". - iMod (owner_update _ _ _ (Some th) with "[$OA $OF]") as "[OA OF]". - iModIntro. iFrame "GT OF". iExists th. iFrame. - Qed. - - Lemma do_unlock γ inv_gname th q : - state γ inv_gname true ** locked γ (Some th) q |-- - (|==> state γ inv_gname false ** not_locked γ th q inv_gname). - Proof. - rewrite /state /locked /not_locked. - iIntros "[State [GT OF]]". - iDestruct "State" as (owner) "(OA & Sets & Balance)". - iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. - injection Heq as ->. - iDestruct (Tokens.release with "[$Balance $GT]") as "[T Balance]". - iModIntro. iFrame "Sets T". iExists (Some th). iFrame. - Qed. - - Lemma unlocked_locked γ inv_gname th q : - state γ inv_gname false ** locked γ th q |-- False. - Proof. - rewrite /state /locked. iIntros "[State [_ OF]]". - iDestruct "State" as (owner) "(_ & OF0 & _)". - iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. - Qed. - - Lemma locked_full_token γ inv_gname : - state γ inv_gname true ** token γ 1 |-- False. - Proof. - rewrite /state /token. iIntros "[State T]". - iDestruct "State" as (th) "(_ & _ & Balance)". - iApply (Tokens.token_not_full_full_token with "[$Balance $T]"). - Qed. - End state_laws. - - #[global] Hint Opaque token not_locked locked state : sl_opacity typeclass_instances. - -End MakeMutexState. - -Module LockState := MakeMutexState MutexSets MutexTokens. + #[global] Hint Opaque owner_tid_auth owner_tid_frag : sl_opacity typeclass_instances. +End OwnerTid. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 233ceabf..8bf80f0c 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -21,21 +21,19 @@ Import linearity. Module mutex_spec (State : lock_ghost2.MUTEX_STATE). Section with_cpp. - Context `{Σ : cpp_logic} {Name : Type}. + Context `{Σ : cpp_logic} {σ : genv} {Name : Type}. Context `{!State.G Σ}. Context (R : Name -> cQp.t -> mpred -> Rep). Context (state_name : Name -> State.gname). - Context {HAS_THREADS : HasStdThreads Σ} {Q : Type}. - (** FIXME is there a more modular way to provide not_locked, locked and ghost - names, e.g. make them parameters to the mutex_spec module? *) - Context (not_locked locked : ptr -> Name -> thread_idT -> Q -> mpred). + Context {HAS_THREADS : HasStdThreads Σ}. (** The guarded predicate must be weakly objective for invariant allocation, which R likely has. *) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{P} ▷P ** [| WeaklyObjective P |] - \post |={⊤}=> Exists g, this |-> R g 1$m P ** State.token (state_name g) 1). + \pre{P γpool} ▷P ** [| WeaklyObjective P |] + \post |={⊤}=> Exists g, [| State.pool_name (state_name g) = γpool |] ** + this |-> R g 1$m P ** State.token (state_name g) 1). Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this @@ -46,47 +44,46 @@ Section with_cpp. (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} not_locked this g thr qt - \post P ** locked this g thr qt). + \pre{qt} State.not_locked this (state_name g) thr qt + \post P ** State.locked this (state_name g) thr qt). Definition unlock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} locked this g thr qt + \pre{qt} State.locked this (state_name g) thr qt \pre ▷P - \post not_locked this g thr qt). + \post State.not_locked this (state_name g) thr qt). Definition try_lock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} not_locked this g thr qt + \pre{qt} State.not_locked this (state_name g) thr qt \post{b}[Vbool b] - if b then P ** locked this g thr qt else not_locked this g thr qt). + if b then P ** State.locked this (state_name g) thr qt + else State.not_locked this (state_name g) thr qt). (* TODO readd the later on the lock/unlock continuations. *) Definition do_lock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** - (locked this lk.1 thr qt ** lk.2 -* K). + ∃ thr qt, current_thread thr ** State.not_locked this (state_name lk.1) thr qt ** + (State.locked this (state_name lk.1) thr qt ** lk.2 -* K). #[global] Arguments do_lock /. Definition do_unlock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** locked this lk.1 thr qt ** ▷lk.2 ** - (not_locked this lk.1 thr qt -* K). + ∃ thr qt, current_thread thr ** State.locked this (state_name lk.1) thr qt ** ▷lk.2 ** + (State.not_locked this (state_name lk.1) thr qt -* K). #[global] Arguments do_unlock /. Definition do_try_lock (this : ptr) (lk : Name * mpred) (K : bool -> mpred) : mpred := - ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + ∃ thr qt, current_thread thr ** State.not_locked this (state_name lk.1) thr qt ** ∀ b : bool, - (if b then lk.2 ** locked this lk.1 thr qt - else not_locked this lk.1 thr qt) -* K b. + (if b then lk.2 ** State.locked this (state_name lk.1) thr qt + else State.not_locked this (state_name lk.1) thr qt) -* K b. #[global] Arguments do_try_lock /. Section equivalences. - Context {σ : genv}. - (* Context (not_locked locked : ptr -> Name -> thread_idT -> Q -> mpred). *) Context (method_name class_name : globname). Context {BL : BasicLockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. @@ -113,7 +110,7 @@ Section with_cpp. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". - iExists q, (g, P), (P ** locked this g thr qt)%I. + iExists q, (g, P), (P ** State.locked this (state_name g) thr qt)%I. iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros "[HL HP]". iFrame. @@ -131,7 +128,7 @@ Section with_cpp. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HL & HP & HK)". - iExists q, (g, P), (not_locked this g thr qt). + iExists q, (g, P), (State.not_locked this (state_name g) thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HL HP". iIntros "$". @@ -152,7 +149,8 @@ Section with_cpp. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". iExists q, (g, P), (fun b : bool => - if b then (P ** locked this g thr qt)%I else not_locked this g thr qt). + if b then (P ** State.locked this (state_name g) thr qt)%I + else State.not_locked this (state_name g) thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros (b) "$". @@ -164,29 +162,28 @@ End mutex_spec. (** Specialize the reusable specs to the standard mutex representation and bind them to their C++ names. *) -Module Mutex (State : lock_ghost2.MUTEX_STATE). +Module StdMutex (State : lock_ghost2.MUTEX_STATE). Module Spec := mutex_spec State. - (** Pair the abstract state with the invariant named by [State.not_locked]. *) - Record gname : Set := MkGname { - lock_state_gname : State.gname; - cinv_gname : iprop.gname; - }. + Definition gname := State.gname. + Definition lock_state_gname (g : gname) := g. + Abbreviation cinv_gname := State.inv_name. Definition G := @State.G. Existing Class G. #[global] Arguments G {_ _} Σ : assert. #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : State.G Σ := H. - Abbreviation token := (fun g q => State.token g.(lock_state_gname) q). - Abbreviation not_locked := (fun (_ : ptr) g th q => State.not_locked g.(lock_state_gname) th q g.(cinv_gname)). - Abbreviation locked := (fun (_ : ptr) g thr q => State.locked g.(lock_state_gname) (Some thr) q). + Abbreviation token := State.token. + Abbreviation not_locked := State.not_locked. + Abbreviation locked := State.locked. Section with_cpp. Context `{Σ : cpp_logic}. (** Fractional ownership of a <> guarding the predicate <

>. *) Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. + #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(cfractional,cfracvalid,ascfractional,type_ptr="std::mutex")] derive R. #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). @@ -207,7 +204,7 @@ Section with_cpp. Context {HAS_THREADS : HasStdThreads Σ}. #[global] Instance locked_learn : - Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> learn_hints.fin) State.locked). + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). Proof. solve_learnable. Qed. cpp.spec "std::mutex::mutex()" as ctor_spec with @@ -217,19 +214,19 @@ Section with_cpp. (\exact Reduce (Spec.dtor_spec R lock_state_gname)). cpp.spec "std::mutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt R not_locked locked)). + (\exact Reduce (Spec.lock_spec_alt R lock_state_gname)). cpp.spec "std::mutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt R not_locked locked)). + (\exact Reduce (Spec.unlock_spec_alt R lock_state_gname)). cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with - (\exact Reduce (Spec.try_lock_spec_alt R not_locked locked)). + (\exact Reduce (Spec.try_lock_spec_alt R lock_state_gname)). - Definition do_lock := Spec.do_lock not_locked locked. + Definition do_lock := Spec.do_lock lock_state_gname. #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock not_locked locked. + Definition do_unlock := Spec.do_unlock lock_state_gname. #[global] Arguments do_unlock /. - Definition do_try_lock := Spec.do_try_lock not_locked locked. + Definition do_try_lock := Spec.do_try_lock lock_state_gname. #[global] Arguments do_try_lock /. (** <> implements [BasicLockable] and [Lockable]. *) @@ -255,22 +252,26 @@ Section with_cpp. Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. Proof. - apply (Spec.lock_spec_entails_lock_spec_alt R not_locked locked). + apply (Spec.lock_spec_entails_lock_spec_alt R lock_state_gname). reflexivity. Qed. Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. Proof. - apply (Spec.unlock_spec_entails_unlock_spec_alt R not_locked locked). + apply (Spec.unlock_spec_entails_unlock_spec_alt R lock_state_gname). reflexivity. Qed. Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. Proof. - apply (Spec.try_lock_spec_entails_try_lock_spec_alt R not_locked locked). + apply (Spec.try_lock_spec_entails_try_lock_spec_alt R lock_state_gname). reflexivity. Qed. End with_cpp. -End Mutex. +End StdMutex. -Module mutex := Mutex lock_ghost2.LockState. +(** The standard-library implementation remains abstract; concrete mutex + implementations instantiate [MUTEX_STATE] beside their proofs. *) +Declare Module StdMutexState : lock_ghost2.MUTEX_STATE with Definition Q := Qp. +Module StdMutexInst := StdMutex StdMutexState. +Module mutex := StdMutexInst. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index e9ad325a..0879d92b 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -1,6 +1,5 @@ (** Provisional *) -Require Import iris.algebra.lib.excl_auth. Require Import iris.algebra.gset. Require Import skylabs.auto.cpp.proof. @@ -15,11 +14,143 @@ NES.Open std.atomic. Import linearity. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. -Module custom_mutex. +(** The ghost implementation and its physical client predicates are specific + to [MyMutex]. The entire mutex-set pool still uses one shared ghost name. *) +Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) + (Owners0 : OWNER_TID) <: MUTEX_STATE. + Record mutex_gname : Set := MkGname { + pool_gname : iprop.gname; + invariant_gname : iprop.gname; + token_gname : iprop.gname; + owner_gname : iprop.gname; + }. + Definition gname : Set := mutex_gname * cQp.t. + Definition Q : Type := cQp.t. + Definition pool_name (γ : gname) : iprop.gname := γ.1.(pool_gname). + Definition inv_name (γ : gname) : iprop.gname := γ.1.(invariant_gname). + + Class stateG `{Σ : cpp_logic} := { + #[global] sets_G :: Sets0.G Σ; + #[global] tokens_G :: Tokens0.G Σ; + #[global] owners_G :: Owners0.G Σ; + }. + Definition G := @stateG. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. + + Definition token `{Σ : cpp_logic, !G Σ} + (γ : gname) (q : Qp) : mpred := + Tokens0.token γ.1.(token_gname) q. + + Definition lock_permit `{Σ : cpp_logic, !G Σ} + (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := + Sets0.mutex_set_frag γ.(pool_gname) th (GSet {[γ.(invariant_gname)]}) ** + Tokens0.token γ.(token_gname) q. + + Definition owner_token `{Σ : cpp_logic, !G Σ} + (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := + Tokens0.given_token γ.(token_gname) q ** + Owners0.owner_tid_frag γ.(owner_gname) (Some th). + + #[global] Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + Proof. rewrite /token. apply Tokens0.token_fractional. Qed. + + #[global] Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + Proof. rewrite /token. apply _. Qed. + + #[global] Instance owner_token_timeless + `{Σ : cpp_logic, !G Σ} γ th q : Timeless (owner_token γ th q). + Proof. rewrite /owner_token. apply _. Qed. + + #[global] Instance owner_token_exclusive + `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => owner_token γ th q). + Proof. + intros th1 th2. rewrite /owner_token. + apply _. + Qed. + + (** While held, the invariant owns this thread's singleton mutex fragment + and the token balance. The thread retains its mutex-set authority. + When free, both halves of the previous owner remain in the invariant. *) + Definition state `{Σ : cpp_logic, !G Σ} + (γ : mutex_gname) (b : bool) : mpred := + (if b then + ∃ th, Owners0.owner_tid_auth γ.(owner_gname) (Some th) ** + Sets0.mutex_set_frag γ.(pool_gname) th (GSet {[γ.(invariant_gname)]}) ** + Tokens0.token_not_full γ.(token_gname) + else + ∃ owner, Owners0.owner_tid_auth γ.(owner_gname) owner ** + Owners0.owner_tid_frag γ.(owner_gname) owner ** + Tokens0.token_full γ.(token_gname))%I. + + #[global] Instance state_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ b : + WeaklyObjective (state γ b). + Proof. rewrite /state. destruct b; apply _. Qed. + + Section state_laws. + Context `{Σ : cpp_logic, !G Σ}. + + Lemma alloc (γpool : iprop.gname) inv_gname : + ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** [| γ.(invariant_gname) = inv_gname |] ** + token (γ, (1$m)%cQp) 1 ** state γ false. + Proof. + iMod Tokens0.alloc as (gt) "[T GT]". + iMod (Owners0.alloc None) as (go) "[OA OF]". + iModIntro. iExists (MkGname γpool inv_gname gt go). + iSplit; first done. iSplit; first done. + rewrite /token /state /=. iFrame "T". iExists None. + iFrame "OA OF". + iApply Tokens0.token_full_init. iExact "GT". + Qed. - Module Spec := mutex_spec LockState. + Lemma do_lock γ th q : + state γ false ** lock_permit γ th q |-- + (|==> state γ true ** owner_token γ th q). + Proof. + rewrite /state /lock_permit /owner_token. + iIntros "[State [Sets T]]". + iDestruct "State" as (owner) "(OA & OF & Balance)". + iDestruct (Tokens0.acquire with "[$Balance $T]") as "[GT Balance]". + iMod (Owners0.owner_update γ.(owner_gname) _ _ (Some th) + with "[$OA $OF]") as "[OA OF]". + iModIntro. iFrame "GT OF". iExists th. iFrame. + Qed. - Abbreviation N := "MyMutex"%cpp_name. + Lemma do_unlock γ th q : + state γ true ** owner_token γ th q |-- + (|==> state γ false ** lock_permit γ th q). + Proof. + rewrite /state /owner_token /lock_permit. + iIntros "[State [GT OF]]". + iDestruct "State" as (owner) "(OA & Sets & Balance)". + iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. + injection Heq as ->. + iDestruct (Tokens0.release with "[$Balance $GT]") as "[T Balance]". + iModIntro. iFrame "Sets T". iExists (Some th). iFrame. + Qed. + + Lemma unlocked_owner_token γ th q : + state γ false ** owner_token γ th q |-- False. + Proof. + rewrite /state /owner_token. iIntros "[State [_ OF]]". + iDestruct "State" as (owner) "(_ & OF0 & _)". + iDestruct (Owners0.owner_tid_frag_exclusive with "OF0 OF") as %[]. + Qed. + + Lemma locked_full_token γ : + state γ true ** token (γ, (1$m)%cQp) 1 |-- False. + Proof. + rewrite /state /token. iIntros "[State T]". + iDestruct "State" as (th) "(_ & _ & Balance)". + iApply (Tokens0.token_not_full_full_token with "[$Balance $T]"). + Qed. + End state_laws. + + #[global] Hint Opaque token lock_permit owner_token state : sl_opacity typeclass_instances. Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) @@ -31,6 +162,47 @@ Module custom_mutex. WeaklyObjective (thread_idR q o p). #[global] Existing Instance thread_idR_WeaklyObjective. + Definition globals `{Σ : cpp_logic} {σ : genv} (q : cQp.t) : mpred := + _global "std::memory_order_seq_cst" |-> + primR "enum std::memory_order" q + (memory_order.to_val memory_order.seq_cst). + + Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (γ : gname) (th : thread_idT) (q : Q) : mpred := + lock_permit γ.1 th γ.2 ** globals q. + #[global] Arguments not_locked /. + #[global] Arguments globals /. + + (** Public ownership includes the physical owner written by [lock]. *) + Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (γ : gname) (th : thread_idT) (q : Q) : mpred := + globals q ** + (this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m (Some th) ** + owner_token γ.1 th γ.2). + #[global] Arguments locked /. + #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + this γ th q : Timeless (locked this γ th q). + Proof. rewrite /locked /globals. apply _. Qed. + #[global] Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this γ q : Exclusive1 (fun th => locked this γ th q). + Proof. + intros th1 th2. rewrite /locked. + apply observe_2_sep_r. apply observe_2_sep_r. apply _. + Qed. + +End CustomMutexState. + +Module LockState := CustomMutexState MutexSets MutexTokens OwnerTid. + +(** Verify the implementation using its ghost resources and public state predicates. *) +Module custom_mutex. + Module State := LockState. + Module Spec := mutex_spec State. + + Abbreviation N := "MyMutex"%cpp_name. + Abbreviation thread_idR := LockState.thread_idR. + #[local] Hint Opaque LockState.thread_idR : sl_opacity typeclass_instances. + #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : WeaklyObjective (p |-> R). @@ -89,52 +261,31 @@ Module custom_mutex. \post emp). End unproved_specs. - Record gname : Set := MkGname - { lock_state_gname : LockState.gname - ; cinv_gname : iprop.gname - }. + Definition gname : Set := State.mutex_gname. + Definition lock_state_gname (γ : gname) : State.gname := (γ, (1$m)%cQp). + Definition cinv_gname : gname -> iprop.gname := State.invariant_gname. Definition lock_namespace : namespace := nroot .@@ "MyMutex". - sl.lock - Definition lockedR `{Σ : cpp_logic} `{!LockState.G Σ} `{σ : genv} - (γ: gname) (thr : thread_idT) (q : cQp.t) : Rep := - _field "MyMutex::m_owner" |-> thread_idR 1$m (Some thr) ** pureR (LockState.locked γ.(lock_state_gname) (Some thr) q%Qp). - #[global] Hint Opaque lockedR : sl_opacity typeclass_instances. - #[only(timeless, exclusive)] derive lockedR. - Section with_Σ. Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, - !LockState.G Σ}. + !State.G Σ}. - (** The invariant holds the thread's mutex set while the spinlock is held. - Its token balance supports both full and partial ownership transfers. *) + (** The physical lock bit agrees with the abstract ghost state. The + protected resources and cleared owner field are available while free. *) Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** - ∃ o_owner : option thread_idT, - LockState.owner_tid_auth γ.(lock_state_gname) o_owner ** - (if b then - ∃ th, - MutexSets.my_mutexes - γ.(lock_state_gname).(LockState.pool_namespace) - γ.(lock_state_gname).(LockState.pool_gname) th {[γ.(cinv_gname)]} ** - [| o_owner = Some th |] ** - MutexTokens.token_not_full γ.(lock_state_gname).(LockState.token_gname) - else - P ** - (** The physical owner is cleared before [do_unlock]; the ghost owner - records the last acquiring thread until the next acquisition. *) - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - LockState.owner_tid_frag γ.(lock_state_gname) o_owner ** - MutexTokens.token_full γ.(lock_state_gname).(LockState.token_gname)). + State.state γ b ** + if b then emp else + P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None. Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR N q$m ** as_Rep (fun this => - cinv lock_namespace γ.(cinv_gname) (mutex_inv this γ P) ** - cinv_own γ.(cinv_gname) q + cinv lock_namespace (cinv_gname γ) (mutex_inv this γ P) ** + cinv_own (cinv_gname γ) q ). Hint Opaque IR : sl_opacity typeclass_instances. #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive IR. @@ -146,7 +297,7 @@ Module custom_mutex. primR "enum std::memory_order" q (memory_order.to_val memory_order.seq_cst)). - Abbreviation token := (fun g q => LockState.token g.(lock_state_gname) q). + Abbreviation token := (fun g q => State.token (g, (1$m)%cQp) q). cpp.spec "MyMutex::MyMutex()" as ctor_spec with (\exact Reduce (Spec.ctor_spec IR lock_state_gname)). @@ -159,36 +310,28 @@ Module custom_mutex. Definition T : Type := gname * cQp.t * mpred. Abbreviation mutexR := (fun (gq : gname * cQp.t) (_ : cQp.t) P => IR gq.1 gq.2 P). - (* the raw assertions are used for the internal do_lock() and do_unlock() *) - Abbreviation not_locked_raw := - (fun (_ : ptr) (gq : gname * cQp.t) thr (_ : unit) => - LockState.not_locked gq.1.(lock_state_gname) thr gq.2 gq.1.(cinv_gname)). - Abbreviation locked_raw := - (fun (this : ptr) (gq : gname * cQp.t) thr (_ : unit) => - (this |-> lockedR gq.1 thr gq.2)%I). - - (** Locking additionally preserves the globals used by the spin loop. *) - Abbreviation not_locked := (fun this gq thr q' => - (not_locked_raw this gq thr tt ** GLOBALS q')%I). - Abbreviation locked := (fun this gq thr q' => - (GLOBALS q' ** locked_raw this gq thr tt)%I). - - Abbreviation raw_locked_raw := - (fun (this : ptr) (gq : gname * cQp.t) thr (_ : unit) => - (this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - LockState.locked gq.1.(lock_state_gname) (Some thr) gq.2)%I). - Abbreviation raw_locked := (fun this gq thr q' => - (GLOBALS q' ** raw_locked_raw this gq thr tt)%I). - - cpp.spec "MyMutex::do_lock()" as do_lock_spec with - (\exact Reduce (Spec.lock_spec_alt mutexR not_locked raw_locked)). - - cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with - (\exact Reduce (Spec.unlock_spec_alt mutexR not_locked_raw raw_locked_raw)). - - Definition do_lock := Spec.do_lock not_locked locked. + cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( + \this this + \prepost{g q P} this |-> IR g q P + \persist{thr} current_thread thr + \pre{qg} State.lock_permit g thr q ** GLOBALS qg + \post P ** GLOBALS qg ** + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + State.owner_token g thr q). + + cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( + \this this + \prepost{g q P} this |-> IR g q P + \persist{thr} current_thread thr + \pre{qg} GLOBALS qg ** + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + State.owner_token g thr q + \pre ▷P + \post State.lock_permit g thr q ** GLOBALS qg). + + Definition do_lock := Spec.do_lock (fun g => g). #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock not_locked_raw locked_raw. + Definition do_unlock := Spec.do_unlock (fun g => g). #[global] Arguments do_unlock /. #[global] Instance custom_mutex_basic_lockable : @@ -198,10 +341,10 @@ Module custom_mutex. ; do_unlock := do_unlock }. cpp.spec "MyMutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt mutexR not_locked locked)). + (\exact Reduce (Spec.lock_spec_alt mutexR (fun g => g))). cpp.spec "MyMutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt mutexR not_locked_raw locked_raw)). + (\exact Reduce (Spec.unlock_spec_alt mutexR (fun g => g))). cpp.spec "MyMutex::lock()" as lock_spec with (\exact Reduce @@ -225,13 +368,13 @@ Module custom_mutex. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P - \consuming LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname) + \consuming State.lock_permit g thr q \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** LockState.locked g.(lock_state_gname) (Some thr) q ** + if bool_decide (res = 0) then P ** State.owner_token g thr q ** p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - else LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)) + else State.lock_permit g thr q) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -244,39 +387,33 @@ Module custom_mutex. iInv lock_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". - iDestruct "Inv" as (b) "[>L Inv]". - iDestruct "Inv" as (oo) "[>OA State]". + iDestruct "Inv" as (b) "(>L & State & Resources)". iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first set_solver. iExists (if b then 1 else 0)%Z. iSplitL "L". { ework $usenamed=true with br_erefl. } iSplit. - iIntros "L". iMod "Y" as "_". - iMod ("Hclose" with "[L OA State]") as "_". + iMod ("Hclose" with "[L State Resources]") as "_". { iNext. rewrite /mutex_inv. iExists b. iSplitL "L"; first by ework $usenamed=true with br_erefl. - iExists oo. iFrame. } + iFrame. } iModIntro. iFrame. - iNext. iIntros "L". iMod "Y" as "_". destruct b. - + iMod ("Hclose" with "[L OA State]") as "_". + + iMod ("Hclose" with "[L State Resources]") as "_". { iNext. rewrite /mutex_inv. iExists true. iSplitL "L"; first by ework $usenamed=true with br_erefl. - iExists oo. iFrame. } + iFrame. } iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. iPureIntro. auto. - + iDestruct "State" as "(P & Owner & OF & Balance)". - iEval (rewrite /LockState.not_locked /LockState.token) in "NL". - iDestruct "NL" as "[Sets T]". - iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". - iMod (LockState.owner_update _ _ _ (Some thr) with "[$OA $OF]") - as "[OA OF]". - iMod ("Hclose" with "[L OA Sets Balance]") as "_". + + iDestruct "Resources" as "[P Owner]". + iMod (State.do_lock with "[$State $NL]") as "[State Locked]". + iMod ("Hclose" with "[L State]") as "_". { iNext. rewrite /mutex_inv. iExists true. iSplitL "L"; first by ework $usenamed=true with br_erefl. - iExists (Some thr). iFrame "OA". - iExists thr. iFrame. done. } - iModIntro. rewrite /IR _at_sep _at_as_Rep /LockState.locked /=. + iFrame. } + iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. iPureIntro. auto. Qed. Hint Resolve do_exchange_C : sl_opacity. @@ -289,10 +426,10 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \consuming LockState.locked g.(lock_state_gname) (Some thr) q + \consuming State.owner_token g thr q \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K - \instantiate K := (p |-> IR g q P ** LockState.not_locked g.(lock_state_gname) thr q g.(cinv_gname)) + \instantiate K := (p |-> IR g q P ** State.lock_permit g thr q) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -300,33 +437,26 @@ Module custom_mutex. iDestruct "Hpre" as "(#Thr & IR & P & Owner & Locked)". iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". iDestruct "IR" as "(S & #CI & CO)". - iEval (rewrite /LockState.locked) in "Locked". - iDestruct "Locked" as "[GT OF]". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc /=. iInv lock_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". - iDestruct "Inv" as (b) "[>L Inv]". - iDestruct "Inv" as (oo) "[>OA State]". + iDestruct "Inv" as (b) "(>L & State & Resources)". iDestruct (fupd_mask_subseteq) as ">Y"; [ | iModIntro ]; first set_solver. iExists (if b then 1 else 0)%Z. iSplitL "L"; first by ework $usenamed=true with br_erefl. iNext. iIntros "L". iMod "Y" as "_". destruct b. - - iDestruct "State" as (owner) "(Sets & %Heq & Balance)". - iDestruct (observe_2 [| oo = Some thr |] with "OA OF") as %Howner. - have -> : owner = thr by congruence. - iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". - iMod ("Hclose" with "[L OA P Owner OF Balance]") as "_". + - iMod (State.do_unlock with "[$State $Locked]") as "[State NL]". + iMod ("Hclose" with "[L State P Owner]") as "_". { iNext. rewrite /mutex_inv. iExists false. iSplitL "L"; first by ework $usenamed=true with br_erefl. - iExists oo. iFrame. by rewrite Howner. } + iFrame. } iModIntro. - rewrite /IR _at_sep _at_as_Rep /LockState.not_locked /=. + rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. - - iDestruct "State" as "(P0 & Owner0 & OF0 & Balance0)". - iDestruct (LockState.owner_tid_frag_exclusive with "OF0 OF") as %[]. + - iDestruct (State.unlocked_owner_token with "[$State $Locked]") as %[]. Qed. Hint Resolve do_store_C : sl_opacity. @@ -352,8 +482,6 @@ Module custom_mutex. Qed. Hint Resolve do_load_C : sl_opacity. - Hint Opaque lockedR : sl_opacity. - Lemma mymutex_do_lock_proof : verify[source] "MyMutex::do_lock()". Proof using MOD HAS_THREADS. verify_spec; go. @@ -369,17 +497,14 @@ Module custom_mutex. Lemma mymutex_lock_alt_proof : verify[source] lock_spec_alt. Proof using MOD HAS_THREADS. verify_spec; ego. - rewrite lockedR.unlock. - ego. - Unshelve. all: first [exact (1$m)%cQp | exact tt]. + Unshelve. all: exact (1$m)%cQp. Qed. Lemma mymutex_unlock_alt_proof : verify[source] unlock_spec_alt. Proof using MOD HAS_THREADS. verify_spec. - rewrite lockedR.unlock. repeat (go; ework). - Unshelve. all: first [exact (1$m)%cQp | exact tt]. + Unshelve. all: exact (1$m)%cQp. Qed. Lemma mymutex_ctor_proof : verify[source] "MyMutex::MyMutex()". @@ -389,21 +514,16 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_idR _ _] "Owner". - iMod (MutexSets.alloc_pool (nroot .@@ "MyMutexPool")) as (gp) "#Pool". - iMod (MutexTokens.alloc) as (gt) "[T GT]". - iMod (own_alloc ((●E None ⋅ ◯E None) : LockState.owner_cmraR)) as (go) "O". - { apply excl_auth_valid. } - iDestruct (own_op with "O") as "[OA OF]". - pose (gs := LockState.MkGname (nroot .@@ "MyMutexPool") gp gt go). iMod (cinv_alloc_cofinite ∅ ⊤ lock_namespace) as (gi) "(_ & CO & Halloc)". - iMod ("Halloc" $! (mutex_inv this (MkGname gs gi) P) - with "[] [L P Owner OA OF GT]") as "#CI". + iMod (State.alloc γpool gi) as (gs) "(%Hpool & %Hinv & T & State)". + iMod ("Halloc" $! (mutex_inv this gs P) + with "[] [L P Owner State]") as "#CI". { iPureIntro. rewrite /mutex_inv. apply _. } - { iNext. rewrite /mutex_inv /=. iExists false. iFrame "L". - iExists None. rewrite /LockState.owner_tid_auth /LockState.owner_tid_frag /gs /=. - iFrame. rewrite /MutexTokens.token_full. iLeft. iExact "GT". } - iModIntro. iExists (MkGname gs gi). - rewrite /IR _at_sep _at_as_Rep /LockState.token /gs /=. + { iNext. rewrite /mutex_inv /=. iExists false. iFrame. } + iModIntro. iExists gs. + iSplit; first done. + subst gi. + rewrite /IR _at_sep _at_as_Rep /cinv_gname /=. iFrame "CI". iFrame. Qed. @@ -414,31 +534,30 @@ Module custom_mutex. work. wname [cinv] "#CI". wname [cinv_own] "CO". - wname [LockState.token] "T". + wname [State.token] "T". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. go. - iDestruct "Inv" as (b) "(Lock & % & OA & State)". - destruct b eqn:Hb. - - iDestruct "State" as (th) "(Sets & %Eq & Balance)". - iEval (rewrite /LockState.token) in "T". - iDestruct (MutexTokens.token_not_full_full_token with "[$Balance $T]") as %[]. - - iDestruct "State" as "(P & Owner & OF & Balance)". + iDestruct "Inv" as (b) "(Lock & State & Resources)". + destruct b. + - iDestruct (State.locked_full_token with "[$State $T]") as %[]. + - iDestruct "Resources" as "[P Owner]". + iAssert emp with "[State T]" as "_". + { iApply (affine with "[State T]"); last iAccu. apply mpred_BiAffine. } ego $usenamed=true with br_erefl. - iApply (affine with "[OA OF Balance T]"); last iAccu. apply mpred_BiAffine. Qed. Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. have -> : lock_spec ⊣⊢ lock_spec_alt. - { apply (Spec.lock_spec_entails_lock_spec_alt mutexR not_locked locked). done. } + { apply (Spec.lock_spec_entails_lock_spec_alt mutexR (fun g => g)). done. } exact mymutex_lock_alt_proof. Qed. Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. have -> : unlock_spec ⊣⊢ unlock_spec_alt. - { apply (Spec.unlock_spec_entails_unlock_spec_alt mutexR not_locked_raw locked_raw). done. } + { apply (Spec.unlock_spec_entails_unlock_spec_alt mutexR (fun g => g)). done. } exact mymutex_unlock_alt_proof. Qed. From 8951a61bf89f0e4fd67e48a17bec1905cfc974cf Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 19:50:15 +0000 Subject: [PATCH 024/109] rename MUTEX_STATE to MUTEX_PREDS --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 32 +----- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 100 ++++++++++++------ .../test/mutex/custom_mutex_hpp_proof.v | 2 +- 3 files changed, 67 insertions(+), 67 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index cf952763..b1a684ab 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -11,7 +11,7 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Import linearity. -(** Ghost state and laws for mutex specs and proofs. *) +(** Various ghost state constructions and laws for concurrency library specs and proofs. *) (** MUTEX_SETS has 2 parts: `mutex_set_map g (T:get thread_idT)` for registering new threads and allocating their `my_mutexes g th` with @@ -172,36 +172,6 @@ Module Type OWNER_TID. (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). End OWNER_TID. -(** A MUTEX_STATE says a mutex spec is parametrized by some `token`, `not_locked` - and `locked`. The exact model depends on the implementation. *) -Module Type MUTEX_STATE. - Parameter gname : Set. - Parameter Q : Type. - (* FIXME do we need these? *) - Parameter pool_name inv_name : gname -> iprop.gname. - - Parameter G : forall `{Σ : cpp_logic}, Type. - Existing Class G. - #[global] Arguments G {_ _} Σ : assert. - - (** [Q] describes the permissions transferred by lock and unlock. *) - Parameter token : forall `{Σ : cpp_logic, !G Σ}, - gname -> Qp -> mpred. - Parameter not_locked locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, - ptr -> gname -> thread_idT -> Q -> mpred. - - #[global] Declare Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). - #[global] Declare Instance token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). - #[global] Declare Instance locked_timeless - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : - Timeless (locked this γ th q). - #[global] Declare Instance locked_exclusive - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ q : - Exclusive1 (fun th => locked this γ th q). -End MUTEX_STATE. - (* Proofs that the ghost state modules are inhabited. *) Module MutexSets : MUTEX_SETS. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 8bf80f0c..59eff024 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -16,15 +16,45 @@ Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Import linearity. +(** A MUTEX_PREDS says a mutex spec is parametrized by some `token`, `not_locked` + and `locked`. The exact model depends on the implementation. *) +Module Type MUTEX_PREDS. + Parameter gname : Set. + Parameter Q : Type. + (* FIXME do we need these? *) + Parameter pool_name inv_name : gname -> iprop.gname. + + Parameter G : forall `{Σ : cpp_logic}, Type. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + + (** [Q] describes the permissions transferred by lock and unlock. *) + Parameter token : forall `{Σ : cpp_logic, !G Σ}, + gname -> Qp -> mpred. + Parameter not_locked locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + ptr -> gname -> thread_idT -> Q -> mpred. + + #[global] Declare Instance token_fractional + `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + #[global] Declare Instance token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + #[global] Declare Instance locked_timeless + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + Timeless (locked this γ th q). + #[global] Declare Instance locked_exclusive + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ q : + Exclusive1 (fun th => locked this γ th q). +End MUTEX_PREDS. + (* TODO UPSTREAM. *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. -Module mutex_spec (State : lock_ghost2.MUTEX_STATE). +Module mutex_spec (Preds : MUTEX_PREDS). Section with_cpp. Context `{Σ : cpp_logic} {σ : genv} {Name : Type}. - Context `{!State.G Σ}. + Context `{!Preds.G Σ}. Context (R : Name -> cQp.t -> mpred -> Rep). - Context (state_name : Name -> State.gname). + Context (state_name : Name -> Preds.gname). Context {HAS_THREADS : HasStdThreads Σ}. (** The guarded predicate must be weakly objective for invariant allocation, @@ -32,55 +62,55 @@ Section with_cpp. Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this \pre{P γpool} ▷P ** [| WeaklyObjective P |] - \post |={⊤}=> Exists g, [| State.pool_name (state_name g) = γpool |] ** - this |-> R g 1$m P ** State.token (state_name g) 1). + \post |={⊤}=> Exists g, [| Preds.pool_name (state_name g) = γpool |] ** + this |-> R g 1$m P ** Preds.token (state_name g) 1). Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{g P} this |-> R g 1$m P ** State.token (state_name g) 1 + \pre{g P} this |-> R g 1$m P ** Preds.token (state_name g) 1 \post P). Definition lock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} State.not_locked this (state_name g) thr qt - \post P ** State.locked this (state_name g) thr qt). + \pre{qt} Preds.not_locked this (state_name g) thr qt + \post P ** Preds.locked this (state_name g) thr qt). Definition unlock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} State.locked this (state_name g) thr qt + \pre{qt} Preds.locked this (state_name g) thr qt \pre ▷P - \post State.not_locked this (state_name g) thr qt). + \post Preds.not_locked this (state_name g) thr qt). Definition try_lock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} State.not_locked this (state_name g) thr qt + \pre{qt} Preds.not_locked this (state_name g) thr qt \post{b}[Vbool b] - if b then P ** State.locked this (state_name g) thr qt - else State.not_locked this (state_name g) thr qt). + if b then P ** Preds.locked this (state_name g) thr qt + else Preds.not_locked this (state_name g) thr qt). (* TODO readd the later on the lock/unlock continuations. *) Definition do_lock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** State.not_locked this (state_name lk.1) thr qt ** - (State.locked this (state_name lk.1) thr qt ** lk.2 -* K). + ∃ thr qt, current_thread thr ** Preds.not_locked this (state_name lk.1) thr qt ** + (Preds.locked this (state_name lk.1) thr qt ** lk.2 -* K). #[global] Arguments do_lock /. Definition do_unlock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** State.locked this (state_name lk.1) thr qt ** ▷lk.2 ** - (State.not_locked this (state_name lk.1) thr qt -* K). + ∃ thr qt, current_thread thr ** Preds.locked this (state_name lk.1) thr qt ** ▷lk.2 ** + (Preds.not_locked this (state_name lk.1) thr qt -* K). #[global] Arguments do_unlock /. Definition do_try_lock (this : ptr) (lk : Name * mpred) (K : bool -> mpred) : mpred := - ∃ thr qt, current_thread thr ** State.not_locked this (state_name lk.1) thr qt ** + ∃ thr qt, current_thread thr ** Preds.not_locked this (state_name lk.1) thr qt ** ∀ b : bool, - (if b then lk.2 ** State.locked this (state_name lk.1) thr qt - else State.not_locked this (state_name lk.1) thr qt) -* K b. + (if b then lk.2 ** Preds.locked this (state_name lk.1) thr qt + else Preds.not_locked this (state_name lk.1) thr qt) -* K b. #[global] Arguments do_try_lock /. Section equivalences. @@ -110,7 +140,7 @@ Section with_cpp. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". - iExists q, (g, P), (P ** State.locked this (state_name g) thr qt)%I. + iExists q, (g, P), (P ** Preds.locked this (state_name g) thr qt)%I. iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros "[HL HP]". iFrame. @@ -128,7 +158,7 @@ Section with_cpp. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HL & HP & HK)". - iExists q, (g, P), (State.not_locked this (state_name g) thr qt). + iExists q, (g, P), (Preds.not_locked this (state_name g) thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HL HP". iIntros "$". @@ -149,8 +179,8 @@ Section with_cpp. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". iExists q, (g, P), (fun b : bool => - if b then (P ** State.locked this (state_name g) thr qt)%I - else State.not_locked this (state_name g) thr qt). + if b then (P ** Preds.locked this (state_name g) thr qt)%I + else Preds.not_locked this (state_name g) thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros (b) "$". @@ -162,21 +192,21 @@ End mutex_spec. (** Specialize the reusable specs to the standard mutex representation and bind them to their C++ names. *) -Module StdMutex (State : lock_ghost2.MUTEX_STATE). - Module Spec := mutex_spec State. +Module StdMutex (Preds : MUTEX_PREDS). + Module Spec := mutex_spec Preds. - Definition gname := State.gname. + Definition gname := Preds.gname. Definition lock_state_gname (g : gname) := g. - Abbreviation cinv_gname := State.inv_name. + Abbreviation cinv_gname := Preds.inv_name. - Definition G := @State.G. + Definition G := @Preds.G. Existing Class G. #[global] Arguments G {_ _} Σ : assert. - #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : State.G Σ := H. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : Preds.G Σ := H. - Abbreviation token := State.token. - Abbreviation not_locked := State.not_locked. - Abbreviation locked := State.locked. + Abbreviation token := Preds.token. + Abbreviation not_locked := Preds.not_locked. + Abbreviation locked := Preds.locked. Section with_cpp. Context `{Σ : cpp_logic}. @@ -271,7 +301,7 @@ End with_cpp. End StdMutex. (** The standard-library implementation remains abstract; concrete mutex - implementations instantiate [MUTEX_STATE] beside their proofs. *) -Declare Module StdMutexState : lock_ghost2.MUTEX_STATE with Definition Q := Qp. + implementations instantiate [MUTEX_PREDS] beside their proofs. *) +Declare Module StdMutexState : MUTEX_PREDS with Definition Q := Qp. Module StdMutexInst := StdMutex StdMutexState. Module mutex := StdMutexInst. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 0879d92b..0aacaadf 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -17,7 +17,7 @@ Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. (** The ghost implementation and its physical client predicates are specific to [MyMutex]. The entire mutex-set pool still uses one shared ghost name. *) Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) - (Owners0 : OWNER_TID) <: MUTEX_STATE. + (Owners0 : OWNER_TID) <: MUTEX_PREDS. Record mutex_gname : Set := MkGname { pool_gname : iprop.gname; invariant_gname : iprop.gname; From 03e10a83ceada6f0f9a4d3db87ef6129fcb3a708 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 21:20:13 +0000 Subject: [PATCH 025/109] code clean up --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 156 +++++++++--------- .../test/mutex/custom_mutex_hpp_proof.v | 92 +++++------ 2 files changed, 119 insertions(+), 129 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 59eff024..7824cf4f 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -20,7 +20,6 @@ Import linearity. and `locked`. The exact model depends on the implementation. *) Module Type MUTEX_PREDS. Parameter gname : Set. - Parameter Q : Type. (* FIXME do we need these? *) Parameter pool_name inv_name : gname -> iprop.gname. @@ -28,14 +27,15 @@ Module Type MUTEX_PREDS. Existing Class G. #[global] Arguments G {_ _} Σ : assert. - (** [Q] describes the permissions transferred by lock and unlock. *) + (** [cQp.t] describes the permissions transferred by lock and unlock. + The permission might involve points-to, so use cQp.t instead of QP. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, - gname -> Qp -> mpred. + gname -> cQp.t -> mpred. Parameter not_locked locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, - ptr -> gname -> thread_idT -> Q -> mpred. + ptr -> gname -> thread_idT -> cQp.t -> mpred. #[global] Declare Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). + `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). #[global] Declare Instance locked_timeless @@ -63,11 +63,11 @@ Section with_cpp. (\this this \pre{P γpool} ▷P ** [| WeaklyObjective P |] \post |={⊤}=> Exists g, [| Preds.pool_name (state_name g) = γpool |] ** - this |-> R g 1$m P ** Preds.token (state_name g) 1). + this |-> R g 1$m P ** Preds.token (state_name g) 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{g P} this |-> R g 1$m P ** Preds.token (state_name g) 1 + \pre{g P} this |-> R g 1$m P ** Preds.token (state_name g) 1$m \post P). Definition lock_spec_alt : ptr -> WpSpec mpred val val := @@ -208,100 +208,94 @@ Module StdMutex (Preds : MUTEX_PREDS). Abbreviation not_locked := Preds.not_locked. Abbreviation locked := Preds.locked. -Section with_cpp. - Context `{Σ : cpp_logic}. + Section with_cpp. + Context `{Σ : cpp_logic}. - (** Fractional ownership of a <> guarding the predicate <

>. *) - Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque R : sl_opacity typeclass_instances. - #[only(cfractional,cfracvalid,ascfractional,type_ptr="std::mutex")] derive R. - #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + (** Fractional ownership of a <> guarding the predicate <

>. *) + Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(cfractional,cfracvalid,ascfractional,type_ptr="std::mutex")] derive R. + #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). - Section with_RepFor. - Import rep.RepFor. - Import RepScheme. + Section with_RepFor. + Import rep.RepFor. + Import RepScheme. - #[global] Instance repfor `{!HasStdThreads Σ} {σ : genv} : - rep.RepFor.C "std::mutex" [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] - (funI γ q P => R γ q P) := {}. - End with_RepFor. + #[global] Instance repfor `{!HasStdThreads Σ} {σ : genv} : + rep.RepFor.C "std::mutex" [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] + (funI γ q P => R γ q P) := {}. + End with_RepFor. - Context `{!G Σ}. + Context `{!G Σ}. - Context `{MOD : source ⊧ σ}. - Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MOD : source ⊧ σ}. + Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Instance locked_learn : - Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). - Proof. solve_learnable. Qed. + #[global] Instance locked_learn : + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). + Proof. solve_learnable. Qed. - cpp.spec "std::mutex::mutex()" as ctor_spec with - (\exact Reduce (Spec.ctor_spec R lock_state_gname)). + cpp.spec "std::mutex::mutex()" as ctor_spec with + (\exact Reduce (Spec.ctor_spec R lock_state_gname)). - cpp.spec "std::mutex::~mutex()" as dtor_spec with - (\exact Reduce (Spec.dtor_spec R lock_state_gname)). + cpp.spec "std::mutex::~mutex()" as dtor_spec with + (\exact Reduce (Spec.dtor_spec R lock_state_gname)). - cpp.spec "std::mutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt R lock_state_gname)). + cpp.spec "std::mutex::lock()" as lock_spec_alt with + (\exact Reduce (Spec.lock_spec_alt R lock_state_gname)). - cpp.spec "std::mutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt R lock_state_gname)). + cpp.spec "std::mutex::unlock()" as unlock_spec_alt with + (\exact Reduce (Spec.unlock_spec_alt R lock_state_gname)). - cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with - (\exact Reduce (Spec.try_lock_spec_alt R lock_state_gname)). + cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with + (\exact Reduce (Spec.try_lock_spec_alt R lock_state_gname)). - Definition do_lock := Spec.do_lock lock_state_gname. - #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock lock_state_gname. - #[global] Arguments do_unlock /. - Definition do_try_lock := Spec.do_try_lock lock_state_gname. - #[global] Arguments do_try_lock /. + Definition do_lock := Spec.do_lock lock_state_gname. + #[global] Arguments do_lock /. + Definition do_unlock := Spec.do_unlock lock_state_gname. + #[global] Arguments do_unlock /. + Definition do_try_lock := Spec.do_try_lock lock_state_gname. + #[global] Arguments do_try_lock /. - (** <> implements [BasicLockable] and [Lockable]. *) - Definition T : Type := gname * mpred. + (** <> implements [BasicLockable] and [Lockable]. *) + Definition T : Type := gname * mpred. - #[global] Instance mutex_basic_lockable : - BasicLockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_lock := do_lock - ; do_unlock := do_unlock }. + #[global] Instance mutex_basic_lockable : + BasicLockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. - cpp.spec "std::mutex::lock()" as lock_spec with - (\exact Reduce (lock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + cpp.spec "std::mutex::lock()" as lock_spec with + (\exact Reduce (lock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - cpp.spec "std::mutex::unlock()" as unlock_spec with - (\exact Reduce (unlock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + cpp.spec "std::mutex::unlock()" as unlock_spec with + (\exact Reduce (unlock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - #[global] Instance mutex_lockable : - Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_try_lock := do_try_lock }. + #[global] Instance mutex_lockable : + Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := + { do_try_lock := do_try_lock }. - cpp.spec "std::mutex::try_lock()" as try_lock_spec with - (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + cpp.spec "std::mutex::try_lock()" as try_lock_spec with + (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. - Proof. - apply (Spec.lock_spec_entails_lock_spec_alt R lock_state_gname). - reflexivity. - Qed. + Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. + Proof. + apply (Spec.lock_spec_entails_lock_spec_alt R lock_state_gname). + reflexivity. + Qed. - Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. - Proof. - apply (Spec.unlock_spec_entails_unlock_spec_alt R lock_state_gname). - reflexivity. - Qed. + Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. + Proof. + apply (Spec.unlock_spec_entails_unlock_spec_alt R lock_state_gname). + reflexivity. + Qed. - Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. - Proof. - apply (Spec.try_lock_spec_entails_try_lock_spec_alt R lock_state_gname). - reflexivity. - Qed. -End with_cpp. + Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. + Proof. + apply (Spec.try_lock_spec_entails_try_lock_spec_alt R lock_state_gname). + reflexivity. + Qed. + End with_cpp. End StdMutex. - -(** The standard-library implementation remains abstract; concrete mutex - implementations instantiate [MUTEX_PREDS] beside their proofs. *) -Declare Module StdMutexState : MUTEX_PREDS with Definition Q := Qp. -Module StdMutexInst := StdMutex StdMutexState. -Module mutex := StdMutexInst. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 0aacaadf..db7db9b1 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -24,10 +24,9 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) token_gname : iprop.gname; owner_gname : iprop.gname; }. - Definition gname : Set := mutex_gname * cQp.t. - Definition Q : Type := cQp.t. - Definition pool_name (γ : gname) : iprop.gname := γ.1.(pool_gname). - Definition inv_name (γ : gname) : iprop.gname := γ.1.(invariant_gname). + Definition gname : Set := mutex_gname. + Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). + Definition inv_name (γ : gname) : iprop.gname := γ.(invariant_gname). Class stateG `{Σ : cpp_logic} := { #[global] sets_G :: Sets0.G Σ; @@ -40,10 +39,10 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. Definition token `{Σ : cpp_logic, !G Σ} - (γ : gname) (q : Qp) : mpred := - Tokens0.token γ.1.(token_gname) q. + (γ : gname) (q : cQp.t) : mpred := + Tokens0.token γ.(token_gname) q. - Definition lock_permit `{Σ : cpp_logic, !G Σ} + Definition not_locked_ghost `{Σ : cpp_logic, !G Σ} (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := Sets0.mutex_set_frag γ.(pool_gname) th (GSet {[γ.(invariant_gname)]}) ** Tokens0.token γ.(token_gname) q. @@ -54,8 +53,11 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Owners0.owner_tid_frag γ.(owner_gname) (Some th). #[global] Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). - Proof. rewrite /token. apply Tokens0.token_fractional. Qed. + `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). + Proof. + intros q1 q2. rewrite /token cQp.frac_add. + apply Tokens0.token_fractional. + Qed. #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). @@ -96,7 +98,7 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Lemma alloc (γpool : iprop.gname) inv_gname : ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** [| γ.(invariant_gname) = inv_gname |] ** - token (γ, (1$m)%cQp) 1 ** state γ false. + token γ 1$m ** state γ false. Proof. iMod Tokens0.alloc as (gt) "[T GT]". iMod (Owners0.alloc None) as (go) "[OA OF]". @@ -108,10 +110,10 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Qed. Lemma do_lock γ th q : - state γ false ** lock_permit γ th q |-- + state γ false ** not_locked_ghost γ th q |-- (|==> state γ true ** owner_token γ th q). Proof. - rewrite /state /lock_permit /owner_token. + rewrite /state /not_locked_ghost /owner_token. iIntros "[State [Sets T]]". iDestruct "State" as (owner) "(OA & OF & Balance)". iDestruct (Tokens0.acquire with "[$Balance $T]") as "[GT Balance]". @@ -122,9 +124,9 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Lemma do_unlock γ th q : state γ true ** owner_token γ th q |-- - (|==> state γ false ** lock_permit γ th q). + (|==> state γ false ** not_locked_ghost γ th q). Proof. - rewrite /state /owner_token /lock_permit. + rewrite /state /owner_token /not_locked_ghost. iIntros "[State [GT OF]]". iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. @@ -142,7 +144,7 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Qed. Lemma locked_full_token γ : - state γ true ** token (γ, (1$m)%cQp) 1 |-- False. + state γ true ** token γ 1$m |-- False. Proof. rewrite /state /token. iIntros "[State T]". iDestruct "State" as (th) "(_ & _ & Balance)". @@ -150,7 +152,7 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Qed. End state_laws. - #[global] Hint Opaque token lock_permit owner_token state : sl_opacity typeclass_instances. + #[global] Hint Opaque token not_locked_ghost owner_token state : sl_opacity typeclass_instances. Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> (* None if value is thread::id(), Some otherwise *) @@ -168,17 +170,17 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) (memory_order.to_val memory_order.seq_cst). Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} - (this : ptr) (γ : gname) (th : thread_idT) (q : Q) : mpred := - lock_permit γ.1 th γ.2 ** globals q. + (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := + not_locked_ghost γ th q ** globals q. #[global] Arguments not_locked /. #[global] Arguments globals /. (** Public ownership includes the physical owner written by [lock]. *) Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} - (this : ptr) (γ : gname) (th : thread_idT) (q : Q) : mpred := + (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := globals q ** (this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m (Some th) ** - owner_token γ.1 th γ.2). + owner_token γ th q). #[global] Arguments locked /. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). @@ -262,7 +264,7 @@ Module custom_mutex. End unproved_specs. Definition gname : Set := State.mutex_gname. - Definition lock_state_gname (γ : gname) : State.gname := (γ, (1$m)%cQp). + Definition lock_state_gname (γ : gname) : State.gname := γ. Definition cinv_gname : gname -> iprop.gname := State.invariant_gname. Definition lock_namespace : namespace := nroot .@@ "MyMutex". @@ -297,62 +299,56 @@ Module custom_mutex. primR "enum std::memory_order" q (memory_order.to_val memory_order.seq_cst)). - Abbreviation token := (fun g q => State.token (g, (1$m)%cQp) q). - cpp.spec "MyMutex::MyMutex()" as ctor_spec with (\exact Reduce (Spec.ctor_spec IR lock_state_gname)). cpp.spec "MyMutex::~MyMutex()" as dtor_spec with (\exact Reduce (Spec.dtor_spec IR lock_state_gname)). - (** Keep the representation fraction in the state, since this implementation - uses the same fraction for [IR] and the locking tokens. *) - Definition T : Type := gname * cQp.t * mpred. - Abbreviation mutexR := (fun (gq : gname * cQp.t) (_ : cQp.t) P => - IR gq.1 gq.2 P). + Definition T : Type := gname * mpred. cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{qg} State.lock_permit g thr q ** GLOBALS qg - \post P ** GLOBALS qg ** + \pre{(qt : cQp.t)} State.not_locked_ghost g thr qt ** GLOBALS qt + \post P ** GLOBALS qt ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - State.owner_token g thr q). + State.owner_token g thr qt). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{qg} GLOBALS qg ** + \pre{qt} GLOBALS qt ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - State.owner_token g thr q + State.owner_token g thr qt \pre ▷P - \post State.lock_permit g thr q ** GLOBALS qg). + \post State.not_locked_ghost g thr qt ** GLOBALS qt). - Definition do_lock := Spec.do_lock (fun g => g). + Definition do_lock := Spec.do_lock lock_state_gname. #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock (fun g => g). + Definition do_unlock := Spec.do_unlock lock_state_gname. #[global] Arguments do_unlock /. #[global] Instance custom_mutex_basic_lockable : BasicLockable (T := T) (Tnamed N) - (fun _ gqP => IR gqP.1.1 gqP.1.2 gqP.2) := + (fun q gP => IR gP.1 q gP.2) := { do_lock := do_lock ; do_unlock := do_unlock }. cpp.spec "MyMutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt mutexR (fun g => g))). + (\exact Reduce (Spec.lock_spec_alt IR lock_state_gname)). cpp.spec "MyMutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt mutexR (fun g => g))). + (\exact Reduce (Spec.unlock_spec_alt IR lock_state_gname)). cpp.spec "MyMutex::lock()" as lock_spec with (\exact Reduce - (lock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). + (lock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). cpp.spec "MyMutex::unlock()" as unlock_spec with (\exact Reduce - (unlock_basic_lockable (Tnamed N) (fun q gqP => IR gqP.1.1 gqP.1.2 gqP.2))). + (unlock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). @@ -368,13 +364,13 @@ Module custom_mutex. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P - \consuming State.lock_permit g thr q + \consuming{qt} State.not_locked_ghost g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** State.owner_token g thr q ** + if bool_decide (res = 0) then P ** State.owner_token g thr qt ** p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - else State.lock_permit g thr q) + else State.not_locked_ghost g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -426,10 +422,10 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \consuming State.owner_token g thr q + \consuming{qt} State.owner_token g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K - \instantiate K := (p |-> IR g q P ** State.lock_permit g thr q) + \instantiate K := (p |-> IR g q P ** State.not_locked_ghost g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -550,14 +546,14 @@ Module custom_mutex. Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. have -> : lock_spec ⊣⊢ lock_spec_alt. - { apply (Spec.lock_spec_entails_lock_spec_alt mutexR (fun g => g)). done. } + { apply (Spec.lock_spec_entails_lock_spec_alt IR lock_state_gname). done. } exact mymutex_lock_alt_proof. Qed. Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. have -> : unlock_spec ⊣⊢ unlock_spec_alt. - { apply (Spec.unlock_spec_entails_unlock_spec_alt mutexR (fun g => g)). done. } + { apply (Spec.unlock_spec_entails_unlock_spec_alt IR lock_state_gname). done. } exact mymutex_unlock_alt_proof. Qed. From bb89743d440e9178466a336f6ae7e14698649d2a Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 21:44:22 +0000 Subject: [PATCH 026/109] more clean up --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 29 +++++++++--------- .../proof/shared_mutex/shared_mutex.v | 4 +-- .../test/mutex/custom_mutex_hpp_proof.v | 30 +++++++------------ 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 7824cf4f..a7cbb6ba 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -20,8 +20,8 @@ Import linearity. and `locked`. The exact model depends on the implementation. *) Module Type MUTEX_PREDS. Parameter gname : Set. - (* FIXME do we need these? *) - Parameter pool_name inv_name : gname -> iprop.gname. + (** Mutex-set pool agreed before mutex creation. *) + Parameter pool_name : gname -> iprop.gname. Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. @@ -42,11 +42,11 @@ Module Type MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). #[global] Declare Instance locked_exclusive - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ q : - Exclusive1 (fun th => locked this γ th q). + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : + Exclusive2 (locked this γ). End MUTEX_PREDS. -(* TODO UPSTREAM. *) +(* TODO UPSTREAM. FIXME what does this do? *) #[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. Module mutex_spec (Preds : MUTEX_PREDS). @@ -127,7 +127,7 @@ Section with_cpp. iSplit; iApply specify_mono; intros this xs K; rewrite Heq; done. Qed. - Lemma lock_spec_entails_lock_spec_alt + Lemma lock_spec_equiv_lock_spec_alt (Hlock : requirements.do_lock (Tnamed class_name) = do_lock) : spec_type_void_to_ret "void" (lock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ @@ -146,7 +146,7 @@ Section with_cpp. iIntros "[HL HP]". iFrame. Qed. - Lemma unlock_spec_entails_unlock_spec_alt + Lemma unlock_spec_equiv_unlock_spec_alt (Hunlock : requirements.do_unlock (Tnamed class_name) = do_unlock) : spec_type_void_to_ret "void" (unlock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ spec_type_void_to_ret "void" unlock_spec_alt. @@ -166,7 +166,7 @@ Section with_cpp. Context {L : Lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. - Lemma try_lock_spec_entails_try_lock_spec_alt + Lemma try_lock_spec_equiv_try_lock_spec_alt (Htry_lock : requirements.do_try_lock (Tnamed class_name) = do_try_lock) : spec_type_void_to_ret "bool" (try_lock_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ spec_type_void_to_ret "bool" try_lock_spec_alt. @@ -197,7 +197,6 @@ Module StdMutex (Preds : MUTEX_PREDS). Definition gname := Preds.gname. Definition lock_state_gname (g : gname) := g. - Abbreviation cinv_gname := Preds.inv_name. Definition G := @Preds.G. Existing Class G. @@ -280,21 +279,21 @@ Module StdMutex (Preds : MUTEX_PREDS). cpp.spec "std::mutex::try_lock()" as try_lock_spec with (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. + Lemma lock_spec_equiv_lock_spec_alt : lock_spec -|- lock_spec_alt. Proof. - apply (Spec.lock_spec_entails_lock_spec_alt R lock_state_gname). + apply (Spec.lock_spec_equiv_lock_spec_alt R lock_state_gname). reflexivity. Qed. - Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. + Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. Proof. - apply (Spec.unlock_spec_entails_unlock_spec_alt R lock_state_gname). + apply (Spec.unlock_spec_equiv_unlock_spec_alt R lock_state_gname). reflexivity. Qed. - Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. + Lemma try_lock_spec_equiv_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. Proof. - apply (Spec.try_lock_spec_entails_try_lock_spec_alt R lock_state_gname). + apply (Spec.try_lock_spec_equiv_try_lock_spec_alt R lock_state_gname). reflexivity. Qed. End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v index e5541e62..423054ea 100644 --- a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v +++ b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v @@ -10,13 +10,11 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.shared_mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Require Import skylabs.brick.libstdcpp.lib.tactics. Import linearity. Import lock_ghost. -(* TODO UPSTREAM. *) -#[global] Instance SplitRecord_prod A B : SplitRecord (@prod A B) := {}. - Module shared_mutex. Section with_cpp. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index db7db9b1..ca0f6e14 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -26,7 +26,6 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) }. Definition gname : Set := mutex_gname. Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). - Definition inv_name (γ : gname) : iprop.gname := γ.(invariant_gname). Class stateG `{Σ : cpp_logic} := { #[global] sets_G :: Sets0.G Σ; @@ -68,10 +67,10 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Proof. rewrite /owner_token. apply _. Qed. #[global] Instance owner_token_exclusive - `{Σ : cpp_logic, !G Σ} γ q : Exclusive1 (fun th => owner_token γ th q). + `{Σ : cpp_logic, !G Σ} γ : Exclusive2 (owner_token γ). Proof. - intros th1 th2. rewrite /owner_token. - apply _. + intros th1 th2 q1 q2. rewrite /owner_token. + apply observe_2_sep_r. apply _. Qed. (** While held, the invariant owns this thread's singleton mutex fragment @@ -186,9 +185,9 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) this γ th q : Timeless (locked this γ th q). Proof. rewrite /locked /globals. apply _. Qed. #[global] Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} - this γ q : Exclusive1 (fun th => locked this γ th q). + this γ : Exclusive2 (locked this γ). Proof. - intros th1 th2. rewrite /locked. + intros th1 th2 q1 q2. rewrite /locked. apply observe_2_sep_r. apply observe_2_sep_r. apply _. Qed. @@ -294,11 +293,6 @@ Module custom_mutex. Context `{MOD : source ⊧ σ}. - Abbreviation GLOBALS q := - (_global "std::memory_order_seq_cst" |-> - primR "enum std::memory_order" q - (memory_order.to_val memory_order.seq_cst)). - cpp.spec "MyMutex::MyMutex()" as ctor_spec with (\exact Reduce (Spec.ctor_spec IR lock_state_gname)). @@ -310,8 +304,8 @@ Module custom_mutex. \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{(qt : cQp.t)} State.not_locked_ghost g thr qt ** GLOBALS qt - \post P ** GLOBALS qt ** + \pre{(qt : cQp.t)} State.not_locked_ghost g thr qt ** State.globals qt + \post P ** State.globals qt ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** State.owner_token g thr qt). @@ -319,11 +313,11 @@ Module custom_mutex. \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{qt} GLOBALS qt ** + \pre{qt} State.globals qt ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** State.owner_token g thr qt \pre ▷P - \post State.not_locked_ghost g thr qt ** GLOBALS qt). + \post State.not_locked_ghost g thr qt ** State.globals qt). Definition do_lock := Spec.do_lock lock_state_gname. #[global] Arguments do_lock /. @@ -354,9 +348,7 @@ Module custom_mutex. Definition bi_later_exist_F := [FWD] @bi.later_exist. Definition bi_later_sep_F := [FWD] @bi.later_sep. - Definition bi_later_sep_B := [BWD->] @bi.later_sep. Hint Resolve bi_later_exist_F bi_later_sep_F : br_hints. - Import linearity. #[program] Definition do_exchange_C (p : ptr) := @@ -546,14 +538,14 @@ Module custom_mutex. Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. have -> : lock_spec ⊣⊢ lock_spec_alt. - { apply (Spec.lock_spec_entails_lock_spec_alt IR lock_state_gname). done. } + { apply (Spec.lock_spec_equiv_lock_spec_alt IR lock_state_gname). done. } exact mymutex_lock_alt_proof. Qed. Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. have -> : unlock_spec ⊣⊢ unlock_spec_alt. - { apply (Spec.unlock_spec_entails_unlock_spec_alt IR lock_state_gname). done. } + { apply (Spec.unlock_spec_equiv_unlock_spec_alt IR lock_state_gname). done. } exact mymutex_unlock_alt_proof. Qed. From f3197b5c250e15b52c4422a8a0b3582d15ac019c Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 21:50:00 +0000 Subject: [PATCH 027/109] add instances to MUTEX_PREDS --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 7 +++++++ .../test/mutex/custom_mutex_hpp_proof.v | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index a7cbb6ba..1f80ae19 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -38,6 +38,13 @@ Module Type MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). + #[global] Declare Instance not_locked_timeless + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + Timeless (not_locked this γ th q). + (** Each thread has at most one handle to attempt locking this mutex. *) + #[global] Declare Instance not_locked_exclusive + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th : + Exclusive1 (not_locked this γ th). #[global] Declare Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index ca0f6e14..d7c1edb2 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -174,6 +174,17 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) #[global] Arguments not_locked /. #[global] Arguments globals /. + #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + this γ th q : Timeless (not_locked this γ th q). + Proof. rewrite /not_locked /not_locked_ghost /globals. apply _. Qed. + #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this γ th : Exclusive1 (not_locked this γ th). + Proof. + intros q1 q2. rewrite /not_locked /not_locked_ghost. + iIntros "[[F1 _] _] [[F2 _] _]". + iDestruct (Sets0.mutex_set_frag_exclusive with "[$F1 $F2]") as %[]. + Qed. + (** Public ownership includes the physical owner written by [lock]. *) Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := From e923867aacfeb797843b527b9662ac5fb05f9171 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 9 Sep 2026 21:50:38 +0000 Subject: [PATCH 028/109] add comments --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 1 + 1 file changed, 1 insertion(+) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 1f80ae19..01692f03 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -224,6 +224,7 @@ Module StdMutex (Preds : MUTEX_PREDS). #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + (* FIXME can we delete this? *) Section with_RepFor. Import rep.RepFor. Import RepScheme. From e19d1cb6b008ddc0fb136894cbfd2ccd4140ff5a Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 10 Sep 2026 05:30:57 +0000 Subject: [PATCH 029/109] make lock_guard and scoped_lock compile --- rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v | 4 ++-- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 5 +++++ rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 48095ffd..287840f6 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -17,7 +17,7 @@ Module lock_guard. sl.lock Definition R `{Σ : cpp_logic, !HasStdThreads Σ} {σ : genv} - (mp : ptr * mutex.gname * Qp * Qp) (q : cQp.t) (P : mpred) : Rep := + (mp : ptr * mutex.gname * Qp * cQp.t) (q : cQp.t) (P : mpred) : Rep := structR "std::lock_guard" q ** let '(mp, g, q', _) := mp in _field "std::lock_guard::_M_device" |-> refR<"std::mutex"> q mp ** @@ -110,7 +110,7 @@ Section with_cpp. Import skylabs.auto.cpp.prelude.proof. - Lemma mutex_borrow mp g P (this : ptr) (q1 q2 qt : Qp) : + Lemma mutex_borrow mp g P (this : ptr) (q1 q2 : Qp) (qt : cQp.t) : this |-> R (mp, g, (q1 + q2)%Qp, qt) 1$m P |-- mp |-> mutex.R g q1$m P ** this |-> R (mp, g, q2, qt) 1$m P. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 01692f03..628d064d 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -306,3 +306,8 @@ Module StdMutex (Preds : MUTEX_PREDS). Qed. End with_cpp. End StdMutex. + +(** The standard-library implementation remains abstract; concrete mutex + implementations supply their own [MUTEX_PREDS]. *) +Declare Module StdMutexPreds : MUTEX_PREDS. +Module mutex := StdMutex StdMutexPreds. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index 9a317ffa..d20ff142 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -9,7 +9,7 @@ Module scoped_lock. Context `{Σ : cpp_logic}. Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - cQp.t -> list (ptr * mutex.gname * Qp * Qp * mpred) -> Rep. + cQp.t -> list (ptr * mutex.gname * Qp * cQp.t * mpred) -> Rep. #[only(type_ptr="std::scoped_lock")] derive R. #[only(cfractional,ascfractional,cfracvalid)] derive R. From 83590fa3452a15c9270687637f10bfeb23fd7c9e Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Sat, 12 Sep 2026 04:04:32 +0000 Subject: [PATCH 030/109] change my_mutexes to track opened masks --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 254 +++++++----------- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 8 +- .../test/mutex/custom_mutex_hpp_proof.v | 66 ++--- 3 files changed, 143 insertions(+), 185 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index b1a684ab..3739c0ba 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -5,6 +5,7 @@ Require Import iris.algebra.gmap. Require Import iris.algebra.gset. Require Import iris.algebra.lib.excl_auth. Require Import iris.algebra.lib.gmap_view. +Require Import iris.algebra.coPset. Require Import skylabs.auto.cpp.proof. Require Export skylabs.brick.libstdcpp.runtime.pred. @@ -13,16 +14,6 @@ Import linearity. (** Various ghost state constructions and laws for concurrency library specs and proofs. *) -(** MUTEX_SETS has 2 parts: `mutex_set_map g (T:get thread_idT)` for registering - new threads and allocating their `my_mutexes g th` with - `mutex_sets_alloc_thread`, which is a pair of `mutex_set_auth` and - `mutex_set_frag` of gnames. - The thread keeps auth and trades a fraction `mutex_set_frag {[ginv]}` to - `inv ginv P` for resources so it only gets resources once from the invariant - until it gives resources back (`mutex_set_frag_exclusive`). - If `ginv` is not allcated yet, it can be allocated with - `my_mutexes_alloc_mutex_name`. -*) Module Type MUTEX_SETS. Parameter cmraR : cmra. @@ -35,62 +26,60 @@ Module Type MUTEX_SETS. Parameter mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, iprop.gname -> gset thread_idT -> mpred. - Parameter mutex_set_frag : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> thread_idT -> gset_disj iprop.gname -> mpred. - Parameter mutex_set_auth : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> thread_idT -> gset_disj iprop.gname -> mpred. - - (** [sa] records mutexes seen by this thread; [sf] contains its available - mutex fragments, which move to lock invariants while locks are held. *) - Definition my_mutexes `{Σ : cpp_logic, !G Σ} γ th sa sf : mpred := - mutex_set_auth γ th sa ** mutex_set_frag γ th sf. - + Parameter my_mutexes : forall `{Σ : cpp_logic, !G Σ}, + iprop.gname -> thread_idT -> coPset.coPset_disj -> mpred. + #[global] Declare Instance mutex_set_map_timeless `{Σ : cpp_logic, !G Σ} γ M : Timeless (mutex_set_map γ M). - #[global] Declare Instance mutex_set_frag_timeless - `{Σ : cpp_logic, !G Σ} γ th s : Timeless (mutex_set_frag γ th s). - #[global] Declare Instance mutex_set_auth_timeless - `{Σ : cpp_logic, !G Σ} γ th s : Timeless (mutex_set_auth γ th s). #[global] Declare Instance my_mutexes_timeless - `{Σ : cpp_logic, !G Σ} γ th sa sf : Timeless (my_mutexes γ th sa sf). + `{Σ : cpp_logic, !G Σ} γ th E : Timeless (my_mutexes γ th E). #[global] Declare Instance mutex_set_map_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ M : WeaklyObjective (mutex_set_map γ M). - #[global] Declare Instance mutex_set_frag_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ th s : WeaklyObjective (mutex_set_frag γ th s). - #[global] Declare Instance mutex_set_auth_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ th s : WeaklyObjective (mutex_set_auth γ th s). #[global] Declare Instance my_mutexes_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ th sa sf : WeaklyObjective (my_mutexes γ th sa sf). + `{Σ : cpp_logic, !G Σ} γ th E : WeaklyObjective (my_mutexes γ th E). - Parameter mutex_set_frag_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th γm, - mutex_set_frag γ th (GSet {[γm]}) ** mutex_set_frag γ th (GSet {[γm]}) |-- False. + Parameter my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), + E1 ∩ E2 <> ∅ -> + my_mutexes γ th (CoPset E1) ** + my_mutexes γ th (CoPset E2) |-- False. Parameter alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, ⊢ |==> ∃ γ, mutex_set_map γ ∅. Parameter mutex_sets_alloc_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, th ∉ T -> mutex_set_map γ T |-- (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (GSet ∅) (GSet ∅)). - Parameter my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} γ th sa sf γm, - γm ∉ sa -> - my_mutexes γ th (GSet sa) sf |-- - (|==> my_mutexes γ th (GSet (sa ∪ {[γm]})) sf ** - mutex_set_frag γ th (GSet {[γm]})). - - (** This is more of a sanity check. Maybe there are better rules that should - be included in the module instead of this. - Distinct threads can allocate, possibly overlapping gname sets. - Frags are given to the lock invariants while auth are held by threads. *) - Parameter mutex_set_frags_alloc : forall `{Σ : cpp_logic, !G Σ} - (th1 th2 : thread_idT) (s1 s2 : gset iprop.gname), + my_mutexes γ th (CoPset ⊤)). + Parameter my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} + γ th (E N : coPset), + N ⊆ E -> + my_mutexes γ th (CoPset E) |-- + my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). + + (* an example that two threads can allocate the same namespace token. *) + Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !G Σ} + (th1 th2 : thread_idT) (N : coPset), th1 ≠ th2 -> ⊢ |==> ∃ γ, - mutex_set_frag γ th1 (GSet s1) ** - mutex_set_frag γ th2 (GSet s2) ** + my_mutexes γ th1 (CoPset N) ** + my_mutexes γ th2 (CoPset N) ** (mutex_set_map γ {[th1; th2]} ** - mutex_set_auth γ th1 (GSet s1) ** - mutex_set_auth γ th2 (GSet s2)). + my_mutexes γ th1 (CoPset (⊤ \ N)) ** + my_mutexes γ th2 (CoPset (⊤ \ N))). + Proof. + intros until N. intros Hneq. + iMod alloc_mutex_set_map as (γ) "Hmap". + iMod (mutex_sets_alloc_thread γ ∅ th1 ltac:(set_solver) + with "Hmap") as "[Hmap Ht1]". + iEval (rewrite left_id_L) in "Hmap". + iMod (mutex_sets_alloc_thread γ {[th1]} th2 ltac:(set_solver) + with "Hmap") as "[Hmap Ht2]". + iDestruct (my_mutexes_alloc_mutex_name γ th1 ⊤ N ltac:(set_solver) + with "Ht1") as "[Hr1 Hn1]". + iDestruct (my_mutexes_alloc_mutex_name γ th2 ⊤ N ltac:(set_solver) + with "Ht2") as "[Hr2 Hn2]". + iModIntro. iExists γ. iFrame. + Qed. End MUTEX_SETS. Module Type MUTEX_TOKENS. @@ -175,9 +164,8 @@ End OWNER_TID. (* Proofs that the ghost state modules are inhabited. *) Module MutexSets : MUTEX_SETS. - Canonical Structure threadR := authUR (gset_disjR iprop.gname). Canonical Structure cmraR : cmra := - discrete_funUR (fun _ : thread_idT => threadR). + discrete_funUR (fun _ : thread_idT => coPset_disjR). Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; @@ -186,20 +174,13 @@ Module MutexSets : MUTEX_SETS. }. #[global] Arguments G {_ _} Σ : assert. + Definition my_mutexes `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (th : thread_idT) (E : coPset_disj) : mpred := + own γ (discrete_fun_singleton th E : cmraR). - Definition mutex_set_auth `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (th : thread_idT) (s : gset_disj iprop.gname) : mpred := - own γ (discrete_fun_singleton th (● s) : cmraR). - - Definition mutex_set_frag `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (th : thread_idT) (s : gset_disj iprop.gname) : mpred := - own γ (discrete_fun_singleton th (◯ s) : cmraR). - - Definition my_mutexes `{Σ : cpp_logic, !G Σ} γ th sa sf : mpred := - mutex_set_auth γ th sa ** mutex_set_frag γ th sf. - + (** The pool reserves the full set for each thread not yet registered. *) Definition reserve (M : gset thread_idT) : cmraR := - fun th => if decide (th ∈ M) then ε else ● (GSet ∅). + fun th => if decide (th ∈ M) then ε else CoPset ⊤. Definition mutex_set_map `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) (M : gset thread_idT) : mpred := @@ -208,43 +189,32 @@ Module MutexSets : MUTEX_SETS. #[global] Instance mutex_set_map_timeless `{Σ : cpp_logic, !G Σ} γ M : Timeless (mutex_set_map γ M). Proof. rewrite /mutex_set_map. apply _. Qed. - #[global] Instance mutex_set_frag_timeless `{Σ : cpp_logic, !G Σ} γ th s : - Timeless (mutex_set_frag γ th s). - Proof. rewrite /mutex_set_frag. apply _. Qed. - #[global] Instance mutex_set_auth_timeless `{Σ : cpp_logic, !G Σ} γ th s : - Timeless (mutex_set_auth γ th s). - Proof. rewrite /mutex_set_auth. apply _. Qed. - #[global] Instance my_mutexes_timeless `{Σ : cpp_logic, !G Σ} γ th sa sf : - Timeless (my_mutexes γ th sa sf). + #[global] Instance my_mutexes_timeless `{Σ : cpp_logic, !G Σ} γ th E : + Timeless (my_mutexes γ th E). Proof. rewrite /my_mutexes. apply _. Qed. #[global] Instance mutex_set_map_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ M : WeaklyObjective (mutex_set_map γ M). Proof. rewrite /mutex_set_map. apply _. Qed. - #[global] Instance mutex_set_frag_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th s : - WeaklyObjective (mutex_set_frag γ th s). - Proof. rewrite /mutex_set_frag. apply _. Qed. - #[global] Instance mutex_set_auth_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th s : - WeaklyObjective (mutex_set_auth γ th s). - Proof. rewrite /mutex_set_auth. apply _. Qed. - #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th sa sf : - WeaklyObjective (my_mutexes γ th sa sf). + #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th E : + WeaklyObjective (my_mutexes γ th E). Proof. rewrite /my_mutexes. apply _. Qed. Section theory. Context `{Σ : cpp_logic, !G Σ}. - Lemma mutex_set_frag_exclusive γ th γm : - mutex_set_frag γ th (GSet {[γm]}) ** - mutex_set_frag γ th (GSet {[γm]}) |-- False. + Lemma my_mutexes_exclusive γ th (E1 E2 : coPset) : + E1 ∩ E2 <> ∅ -> + my_mutexes γ th (CoPset E1) ** + my_mutexes γ th (CoPset E2) |-- False. Proof. - rewrite /mutex_set_frag. - iIntros "[H1 H2]". + rewrite /my_mutexes. + iIntros (Hoverlap) "[H1 H2]". iDestruct (own_valid_2 with "H1 H2") as %Hvalid. iPureIntro. specialize (Hvalid th). rewrite discrete_fun_lookup_op !discrete_fun_lookup_singleton in Hvalid. - rewrite -auth_frag_op auth_frag_valid gset_disj_valid_op in Hvalid. + rewrite coPset_disj_valid_op in Hvalid. set_solver. Qed. @@ -252,55 +222,30 @@ Module MutexSets : MUTEX_SETS. ⊢ |==> ∃ γ, mutex_set_map γ ∅. Proof. iMod (own_alloc (reserve ∅)) as (γ) "Hmap". - { intros th. rewrite /reserve. - apply auth_auth_valid. done. } + { intros th. rewrite /reserve. case_decide; done. } iModIntro. iExists γ. iExact "Hmap". Qed. - Lemma my_mutexes_alloc_mutex_name γ th sa sf γm : - γm ∉ sa -> - my_mutexes γ th (GSet sa) sf |-- - (|==> my_mutexes γ th (GSet (sa ∪ {[γm]})) sf ** - mutex_set_frag γ th (GSet {[γm]})). - Proof. - rewrite /my_mutexes /mutex_set_auth /mutex_set_frag. - iIntros (Hfresh) "[HA HF]". - iMod (own_update γ _ - ((discrete_fun_singleton th (● GSet (sa ∪ {[γm]})) ⋅ - discrete_fun_singleton th (◯ GSet {[γm]})) : cmraR) - with "HA") as "[HA Hnew]". - { rewrite discrete_fun_singleton_op. - apply discrete_fun_singleton_update. - rewrite (comm_L union). - apply auth_update_alloc. - apply gset_disj_alloc_empty_local_update. set_solver. } - iModIntro. iFrame. - Qed. - - Lemma mutex_sets_alloc_thread_with_set - γ (T : gset thread_idT) th (s : gset iprop.gname) : + Lemma mutex_sets_alloc_thread γ T th : th ∉ T -> mutex_set_map γ T |-- (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (GSet s) (GSet s)). + my_mutexes γ th (CoPset ⊤)). Proof. - rewrite /mutex_set_map /my_mutexes /mutex_set_auth /mutex_set_frag. + rewrite /mutex_set_map /my_mutexes. iIntros (Hfresh) "Hmap". iMod (own_update γ _ (reserve (T ∪ {[th]}) ⋅ - (discrete_fun_singleton th (● GSet s) ⋅ discrete_fun_singleton th (◯ GSet s))) - with "Hmap") as "[Hmap [HA HF]]". + discrete_fun_singleton th (CoPset ⊤)) + with "Hmap") as "[Hmap Ht]". { apply discrete_fun_update. intros th'. - rewrite !discrete_fun_lookup_op. + rewrite discrete_fun_lookup_op. destruct (decide (th = th')) as [<-|Hne]. - - rewrite !discrete_fun_lookup_singleton /reserve. - case_decide; first contradiction. - case_decide; last set_solver. - rewrite left_id. - apply auth_update_alloc. - rewrite -{1}(right_id_L ∅ union s). - apply gset_disj_alloc_empty_local_update. set_solver. - - rewrite !discrete_fun_lookup_singleton_ne; try done. - rewrite left_id right_id /reserve. + - rewrite discrete_fun_lookup_singleton /reserve. + rewrite decide_False; last done. + rewrite decide_True; last set_solver. + by rewrite left_id. + - rewrite discrete_fun_lookup_singleton_ne; last done. + rewrite right_id /reserve. destruct (decide (th' ∈ T)). + rewrite !decide_True; try set_solver. + rewrite !decide_False; try set_solver. @@ -308,38 +253,47 @@ Module MutexSets : MUTEX_SETS. iModIntro. iFrame. Qed. - Lemma mutex_sets_alloc_thread γ T th : - th ∉ T -> - mutex_set_map γ T |-- - (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (GSet ∅) (GSet ∅)). - Proof. apply mutex_sets_alloc_thread_with_set. Qed. - - Lemma mutex_set_frags_alloc (th1 th2 : thread_idT) (s1 s2 : gset iprop.gname) : - th1 ≠ th2 -> - ⊢ |==> ∃ γ, - mutex_set_frag γ th1 (GSet s1) ** - mutex_set_frag γ th2 (GSet s2) ** - (mutex_set_map γ {[th1; th2]} ** - mutex_set_auth γ th1 (GSet s1) ** - mutex_set_auth γ th2 (GSet s2)). + Lemma my_mutexes_alloc_mutex_name γ th (E N : coPset) : + N ⊆ E -> + my_mutexes γ th (CoPset E) |-- + my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). Proof. - iIntros (Hneq). - iMod alloc_mutex_set_map as (γ) "Hmap". - iMod (mutex_sets_alloc_thread_with_set γ ∅ th1 s1 ltac:(set_solver) - with "Hmap") as "[Hmap Ht1]". - iEval (rewrite left_id_L) in "Hmap". - iMod (mutex_sets_alloc_thread_with_set γ {[th1]} th2 s2 ltac:(set_solver) - with "Hmap") as "[Hmap Ht2]". - iDestruct "Ht1" as "[Ha1 Hf1]". - iDestruct "Ht2" as "[Ha2 Hf2]". - iModIntro. iExists γ. iFrame. + intros Hsub. + rewrite /my_mutexes -own_op discrete_fun_singleton_op. + rewrite coPset_disj_union; last set_solver. + rewrite difference_union_L. + have -> : E ∪ N = E by set_solver. + done. Qed. End theory. - #[global] Hint Opaque mutex_set_map mutex_set_auth mutex_set_frag - my_mutexes : sl_opacity typeclass_instances. + (* an example that two threads can allocate the same namespace token. *) + Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !G Σ} + (th1 th2 : thread_idT) (N : coPset), + th1 ≠ th2 -> + ⊢ |==> ∃ γ, + my_mutexes γ th1 (CoPset N) ** + my_mutexes γ th2 (CoPset N) ** + (mutex_set_map γ {[th1; th2]} ** + my_mutexes γ th1 (CoPset (⊤ \ N)) ** + my_mutexes γ th2 (CoPset (⊤ \ N))). + Proof. + intros until N. intros Hneq. + iMod alloc_mutex_set_map as (γ) "Hmap". + iMod (mutex_sets_alloc_thread γ ∅ th1 ltac:(set_solver) + with "Hmap") as "[Hmap Ht1]". + iEval (rewrite left_id_L) in "Hmap". + iMod (mutex_sets_alloc_thread γ {[th1]} th2 ltac:(set_solver) + with "Hmap") as "[Hmap Ht2]". + iDestruct (my_mutexes_alloc_mutex_name γ th1 ⊤ N ltac:(set_solver) + with "Ht1") as "[Hr1 Hn1]". + iDestruct (my_mutexes_alloc_mutex_name γ th2 ⊤ N ltac:(set_solver) + with "Ht2") as "[Hr2 Hn2]". + iModIntro. iExists γ. iFrame. + Qed. + + #[global] Hint Opaque mutex_set_map my_mutexes : sl_opacity typeclass_instances. End MutexSets. (** ** The fractional token/given-token pair *) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 628d064d..2d530ffd 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -18,10 +18,12 @@ Import linearity. (** A MUTEX_PREDS says a mutex spec is parametrized by some `token`, `not_locked` and `locked`. The exact model depends on the implementation. *) + +(* TODO add my_mutexes rules to MUTEX_PREDS *) Module Type MUTEX_PREDS. Parameter gname : Set. (** Mutex-set pool agreed before mutex creation. *) - Parameter pool_name : gname -> iprop.gname. + Parameter mutex_inv_namespace : namespace. Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. @@ -68,8 +70,8 @@ Section with_cpp. which R likely has. *) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{P γpool} ▷P ** [| WeaklyObjective P |] - \post |={⊤}=> Exists g, [| Preds.pool_name (state_name g) = γpool |] ** + \pre{P} ▷P ** [| WeaklyObjective P |] + \post |={⊤}=> Exists g, this |-> R g 1$m P ** Preds.token (state_name g) 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index d7c1edb2..475988b4 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -1,6 +1,6 @@ (** Provisional *) -Require Import iris.algebra.gset. +Require Import iris.algebra.coPset. Require Import skylabs.auto.cpp.proof. Require Import skylabs.auto.cpp.hints.base_derived. @@ -20,13 +20,15 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) (Owners0 : OWNER_TID) <: MUTEX_PREDS. Record mutex_gname : Set := MkGname { pool_gname : iprop.gname; - invariant_gname : iprop.gname; token_gname : iprop.gname; owner_gname : iprop.gname; }. Definition gname : Set := mutex_gname. Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). + Definition mutex_inv_namespace : namespace := + nroot .@@ "MyMutex" .@ "inv_namespace". + Class stateG `{Σ : cpp_logic} := { #[global] sets_G :: Sets0.G Σ; #[global] tokens_G :: Tokens0.G Σ; @@ -43,7 +45,7 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Definition not_locked_ghost `{Σ : cpp_logic, !G Σ} (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := - Sets0.mutex_set_frag γ.(pool_gname) th (GSet {[γ.(invariant_gname)]}) ** + Sets0.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** Tokens0.token γ.(token_gname) q. Definition owner_token `{Σ : cpp_logic, !G Σ} @@ -73,14 +75,14 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) apply observe_2_sep_r. apply _. Qed. - (** While held, the invariant owns this thread's singleton mutex fragment - and the token balance. The thread retains its mutex-set authority. + (** While held, the invariant owns this thread's singleton mutex set + and the token balance. The thread retains its remaining mutex names. When free, both halves of the previous owner remain in the invariant. *) Definition state `{Σ : cpp_logic, !G Σ} (γ : mutex_gname) (b : bool) : mpred := (if b then ∃ th, Owners0.owner_tid_auth γ.(owner_gname) (Some th) ** - Sets0.mutex_set_frag γ.(pool_gname) th (GSet {[γ.(invariant_gname)]}) ** + Sets0.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** Tokens0.token_not_full γ.(token_gname) else ∃ owner, Owners0.owner_tid_auth γ.(owner_gname) owner ** @@ -95,14 +97,14 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Section state_laws. Context `{Σ : cpp_logic, !G Σ}. - Lemma alloc (γpool : iprop.gname) inv_gname : - ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** [| γ.(invariant_gname) = inv_gname |] ** + Lemma alloc (γpool : iprop.gname) : + ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** token γ 1$m ** state γ false. Proof. iMod Tokens0.alloc as (gt) "[T GT]". iMod (Owners0.alloc None) as (go) "[OA OF]". - iModIntro. iExists (MkGname γpool inv_gname gt go). - iSplit; first done. iSplit; first done. + iModIntro. iExists (MkGname γpool gt go). + iSplit; first done. rewrite /token /state /=. iFrame "T". iExists None. iFrame "OA OF". iApply Tokens0.token_full_init. iExact "GT". @@ -182,7 +184,11 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Proof. intros q1 q2. rewrite /not_locked /not_locked_ghost. iIntros "[[F1 _] _] [[F2 _] _]". - iDestruct (Sets0.mutex_set_frag_exclusive with "[$F1 $F2]") as %[]. + have Hnonempty : (↑ mutex_inv_namespace : coPset) ∩ ↑ mutex_inv_namespace ≠ ∅. + { rewrite intersection_idemp_L. apply nclose_non_empty. } + iDestruct (Sets0.my_mutexes_exclusive γ.(pool_gname) th + (↑ mutex_inv_namespace) (↑ mutex_inv_namespace) Hnonempty + with "[$F1 $F2]") as %[]. Qed. (** Public ownership includes the physical owner written by [lock]. *) @@ -273,11 +279,10 @@ Module custom_mutex. \post emp). End unproved_specs. - Definition gname : Set := State.mutex_gname. - Definition lock_state_gname (γ : gname) : State.gname := γ. - Definition cinv_gname : gname -> iprop.gname := State.invariant_gname. - - Definition lock_namespace : namespace := nroot .@@ "MyMutex". + Record gname : Set := MkGname { + lock_state_gname :> State.mutex_gname; + cinv_gname : iprop.gname; + }. Section with_Σ. Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, @@ -296,7 +301,7 @@ Module custom_mutex. Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR N q$m ** as_Rep (fun this => - cinv lock_namespace (cinv_gname γ) (mutex_inv this γ P) ** + cinv State.mutex_inv_namespace (cinv_gname γ) (mutex_inv this γ P) ** cinv_own (cinv_gname γ) q ). Hint Opaque IR : sl_opacity typeclass_instances. @@ -383,7 +388,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_exchange. iAuIntro1. rewrite /atomic1_acc. - iInv lock_namespace as "Inv" "Hclose". + iInv State.mutex_inv_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -407,7 +412,7 @@ Module custom_mutex. iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. iPureIntro. auto. + iDestruct "Resources" as "[P Owner]". - iMod (State.do_lock with "[$State $NL]") as "[State Locked]". + iMod (State.do_lock _ with "[$State $NL]") as "[State Locked]". iMod ("Hclose" with "[L State]") as "_". { iNext. rewrite /mutex_inv. iExists true. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -438,7 +443,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc /=. - iInv lock_namespace as "Inv" "Hclose". + iInv State.mutex_inv_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -447,7 +452,7 @@ Module custom_mutex. iSplitL "L"; first by ework $usenamed=true with br_erefl. iNext. iIntros "L". iMod "Y" as "_". destruct b. - - iMod (State.do_unlock with "[$State $Locked]") as "[State NL]". + - iMod (State.do_unlock _ with "[$State $Locked]") as "[State NL]". iMod ("Hclose" with "[L State P Owner]") as "_". { iNext. rewrite /mutex_inv. iExists false. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -513,17 +518,14 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_idR _ _] "Owner". - iMod (cinv_alloc_cofinite ∅ ⊤ lock_namespace) as (gi) "(_ & CO & Halloc)". - iMod (State.alloc γpool gi) as (gs) "(%Hpool & %Hinv & T & State)". - iMod ("Halloc" $! (mutex_inv this gs P) - with "[] [L P Owner State]") as "#CI". - { iPureIntro. rewrite /mutex_inv. apply _. } - { iNext. rewrite /mutex_inv /=. iExists false. iFrame. } - iModIntro. iExists gs. - iSplit; first done. - subst gi. - rewrite /IR _at_sep _at_as_Rep /cinv_gname /=. - iFrame "CI". iFrame. + iMod (State.alloc 1%positive) as (gs) "(_ & T & State)". + iMod (cinv_alloc with "[L P Owner State]") + as (gi) "[#CI CO]"; last first. + - iModIntro. iExists (MkGname gs gi). + rewrite /IR _at_sep _at_as_Rep /mutex_inv /=. + iFrame "CI". iFrame. + - iNext. iExists false. iFrame. + - apply _. Qed. Lemma mymutex_dtor_proof : verify[source] "MyMutex::~MyMutex()". From ec5a8ced2fb00824fce05d2a4e180b7476a9a2f4 Mon Sep 17 00:00:00 2001 From: Ke Du Date: Mon, 14 Sep 2026 16:22:50 -0500 Subject: [PATCH 031/109] add mutex client --- rocq-brick-libstdcpp/proof/thread/spec.v | 65 +++++++++++++++++++ rocq-brick-libstdcpp/proof/thread/thread.hpp | 1 + .../test/mutex/double_incr.cpp | 24 +++++++ 3 files changed, 90 insertions(+) create mode 100644 rocq-brick-libstdcpp/proof/thread/thread.hpp create mode 100644 rocq-brick-libstdcpp/test/mutex/double_incr.cpp diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index e69de29b..a2b01d3c 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -0,0 +1,65 @@ +Require Import skylabs.bi.tls_modalities. +Require Import skylabs.auto.cpp.prelude.proof. +(* Require Export skylabs.brick.libstdcpp.thread.pred. *) +Require Import skylabs.brick.libstdcpp.runtime.pred. +Require Import skylabs.brick.libstdcpp.thread.thread_hpp. + +(** Specifications for the nullary function-pointer specialization of + <>. [spawn_spec] is the constructor contract, to be bound to + the constructor specialization in the client's translation unit. The + forwarding-reference argument points to a function-pointer object; the + constructor copies its value before starting the child. + + These are normal-return contracts. They do not specify creation failures, + arbitrary callable objects, argument decay/copying, or detach. *) +Section with_cpp. + Context `{Σ : cpp_logic} {σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + + (** Exclusive ownership of a thread object. [None] is non-joinable; + [Some (child, Q)] owns the right to join [child] and receive [Q], + interpreted in that child's thread context. In particular, this + predicate must not be declared persistent or fractionally splittable. *) + Parameter R : option (thread_idT * mpred) -> Rep. + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(type_ptr="std::thread")] derive R. + #[global] Declare Instance R_exclusive state (this : ptr) : + Exclusive0 (this |-> R state). + + Definition entry_type : type := Tfunction (FunctionType Tvoid []). + + (** The obligation is linear: resources needed by the child are consumed + at spawn. Quantification allows the runtime to choose the child's ID. + The modality prevents the parent's thread-local facts from being + silently reused in the child. *) + Definition spawn_spec : ptr -> WpSpec mpred val val := + (\this this + \arg{fp : ptr} "f" (Vptr fp) + \prepost{q f} fp |-> primR (Tptr entry_type) q (Vptr f) + \persist{parent} current_thread parent + \pre{Q : thread_idT -> mpred} + (∀ child, [| child <> parent |] -* + @(threadTI, child) + (wp_fptr (σ.(genv_tu).(types)) entry_type f [] + (fun _ => Q child))) + \post Exists child, + [| child <> parent |] ** this |-> R (Some (child, Q child))). + + (** Joining consumes the join right exactly once and leaves the object + non-joinable. Keep the child's modality on [Q]: join does not turn + child-local ownership into ownership local to the caller. *) + Definition join_spec : ptr -> WpSpec mpred val val := + (\this this + \persist{parent} current_thread parent + \pre{child Q} this |-> R (Some (child, Q)) + \require child <> parent + \post this |-> R None ** @(threadTI, child) Q). + + (** Supply the instantiated constructor name from the translation unit. *) + cpp.spec "std::thread::thread()" as ctor_spec with + (\exact Reduce spawn_spec). + + Definition join : mpred := + specify {| info_name := "std::thread::join()"; + info_type := tMethod "std::thread" QM "void" [] |} join_spec. +End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/thread/thread.hpp b/rocq-brick-libstdcpp/proof/thread/thread.hpp new file mode 100644 index 00000000..ee6744fe --- /dev/null +++ b/rocq-brick-libstdcpp/proof/thread/thread.hpp @@ -0,0 +1 @@ +#include \ No newline at end of file diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr.cpp b/rocq-brick-libstdcpp/test/mutex/double_incr.cpp new file mode 100644 index 00000000..0d7a822f --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/double_incr.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +unsigned int x = 0; +std::mutex m; + +void double_incr() { + m.lock(); + x++; + x++; + m.unlock(); +} + +int main() { + std::thread t1(double_incr); + std::thread t2(double_incr); + + t1.join(); + t2.join(); + + assert(x == 4); + return 0; +} From 37c20f74c40064c439e77e27de13aa19a5be528d Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 16 Sep 2026 02:56:12 +0000 Subject: [PATCH 032/109] [mutex.v] move mutex predicates to MUTEX_PREDS if they are not specific to implementation; add my_mutexes allocation rule and mutex invariant initalization rule --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 271 +++++++++--------- 1 file changed, 136 insertions(+), 135 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 2d530ffd..5ec907aa 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -19,9 +19,9 @@ Import linearity. (** A MUTEX_PREDS says a mutex spec is parametrized by some `token`, `not_locked` and `locked`. The exact model depends on the implementation. *) -(* TODO add my_mutexes rules to MUTEX_PREDS *) Module Type MUTEX_PREDS. Parameter gname : Set. + Parameter cpp_ty : type. (** Mutex-set pool agreed before mutex creation. *) Parameter mutex_inv_namespace : namespace. @@ -29,6 +29,9 @@ Module Type MUTEX_PREDS. Existing Class G. #[global] Arguments G {_ _} Σ : assert. + (** Registration uses the same mutex-set model as the state predicates. *) + #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. + (** [cQp.t] describes the permissions transferred by lock and unlock. The permission might involve points-to, so use cQp.t instead of QP. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, @@ -53,6 +56,80 @@ Module Type MUTEX_PREDS. #[global] Declare Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). + + (** The thread-registration pool used by this mutex. *) + Parameter pool_name : gname -> iprop.gname. + + Section with_cpp. + Context `{Σ : cpp_logic}. + + (** Fractional ownership of a mutex guarding the predicate <

>. *) + Parameter R : forall `{!G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. + Context `{!G Σ}. + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(cfractional,cfracvalid,ascfractional)] derive R. + (* #[global] Declare Instance R_type_ptr : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv} g q P, + Typed class_name (R g q P). *) + #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + + Context {σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + + #[global] Instance locked_learn : + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). + Proof. solve_learnable. Qed. + + (* TODO readd the later on the lock/unlock continuations. *) + Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + (locked this lk.1 thr qt ** lk.2 -* K). + #[global] Arguments do_lock /. + + Definition do_unlock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** locked this lk.1 thr qt ** ▷lk.2 ** + (not_locked this lk.1 thr qt -* K). + #[global] Arguments do_unlock /. + + #[global] Instance mutex_basic_lockable : + BasicLockable (T := gname * mpred) cpp_ty + (fun q gP => R gP.1 q gP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. + + Definition do_try_lock (this : ptr) (lk : gname * mpred) + (K : bool -> mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + ∀ b : bool, + (if b then lk.2 ** locked this lk.1 thr qt + else not_locked this lk.1 thr qt) -* K b. + #[global] Arguments do_try_lock /. + + #[global] Instance mutex_lockable : + Lockable (T:=gname * mpred) cpp_ty (λ q γP, R γP.1 q γP.2) := + { do_try_lock := do_try_lock }. + + (** Install [P] in an initialized mutex using its full representation and + token. The physical mutex remains initialized and unlocked. *) + Parameter install : forall (this : ptr) (old : gname) + (pool : iprop.gname) (P : mpred), + WeaklyObjective P -> + this |-> R old 1$m emp ** token old 1$m ** ▷P |-- + (|={⊤}=> ∃ g, + this |-> R g 1$m P ** token g 1$m). + + (** Register a thread by consuming its handle for this mutex's namespace + and its token share. [my_mutexes_alloc_mutex_name] splits this handle + from the full namespace pool supplied when the thread is spawned. *) + Parameter register_thread : forall + (this : ptr) (g : gname) (q : cQp.t) (P : mpred) + (th : thread_idT) (qt : cQp.t), + this |-> R g q P ** token g qt ** + MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace) |-- + this |-> R g q P ** not_locked this g th qt. + + End with_cpp. End MUTEX_PREDS. (* TODO UPSTREAM. FIXME what does this do? *) @@ -60,136 +137,102 @@ End MUTEX_PREDS. Module mutex_spec (Preds : MUTEX_PREDS). Section with_cpp. - Context `{Σ : cpp_logic} {σ : genv} {Name : Type}. - Context `{!Preds.G Σ}. - Context (R : Name -> cQp.t -> mpred -> Rep). - Context (state_name : Name -> Preds.gname). + Import Preds. + Context `{Σ : cpp_logic} {σ : genv}. + Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. + (** The guarded predicate must be weakly objective for invariant allocation, which R likely has. *) - Definition ctor_spec : ptr -> WpSpec mpred val val := - (\this this - \pre{P} ▷P ** [| WeaklyObjective P |] - \post |={⊤}=> Exists g, - this |-> R g 1$m P ** Preds.token (state_name g) 1$m). + Definition ctor_spec (this : ptr) : WpSpec mpred val val := + (\pre{P} ▷P + \require WeaklyObjective P + \post |={⊤}=> Exists g, + this |-> R g 1$m P ** token g 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{g P} this |-> R g 1$m P ** Preds.token (state_name g) 1$m + \pre{g P} this |-> R g 1$m P ** token g 1$m \post P). Definition lock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} Preds.not_locked this (state_name g) thr qt - \post P ** Preds.locked this (state_name g) thr qt). + \pre{qt} not_locked this g thr qt + \post P ** locked this g thr qt). Definition unlock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} Preds.locked this (state_name g) thr qt + \pre{qt} locked this g thr qt \pre ▷P - \post Preds.not_locked this (state_name g) thr qt). + \post not_locked this g thr qt). Definition try_lock_spec_alt : ptr -> WpSpec mpred val val := (\this this \prepost{q P g} this |-> R g q P \persist{thr} current_thread thr - \pre{qt} Preds.not_locked this (state_name g) thr qt + \pre{qt} not_locked this g thr qt \post{b}[Vbool b] - if b then P ** Preds.locked this (state_name g) thr qt - else Preds.not_locked this (state_name g) thr qt). - - (* TODO readd the later on the lock/unlock continuations. *) - Definition do_lock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** Preds.not_locked this (state_name lk.1) thr qt ** - (Preds.locked this (state_name lk.1) thr qt ** lk.2 -* K). - #[global] Arguments do_lock /. - - Definition do_unlock (this : ptr) (lk : Name * mpred) (K : mpred) : mpred := - ∃ thr qt, current_thread thr ** Preds.locked this (state_name lk.1) thr qt ** ▷lk.2 ** - (Preds.not_locked this (state_name lk.1) thr qt -* K). - #[global] Arguments do_unlock /. - - Definition do_try_lock (this : ptr) (lk : Name * mpred) - (K : bool -> mpred) : mpred := - ∃ thr qt, current_thread thr ** Preds.not_locked this (state_name lk.1) thr qt ** - ∀ b : bool, - (if b then lk.2 ** Preds.locked this (state_name lk.1) thr qt - else Preds.not_locked this (state_name lk.1) thr qt) -* K b. - #[global] Arguments do_try_lock /. + if b then P ** locked this g thr qt + else not_locked this g thr qt). - Section equivalences. - Context (method_name class_name : globname). - Context {BL : BasicLockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. - - Definition spec_type_void_to_ret := (fun ret => specify {| info_name := method_name; info_type := tMethod class_name QM ret [] |}). - - Local Lemma method_spec_equiv (ret : type) - (Pspec Qspec : ptr -> WpSpec mpred val val) - (Heq : forall this xs K, Pspec this xs K ⊣⊢ Qspec this xs K) : - spec_type_void_to_ret ret Pspec ⊣⊢ spec_type_void_to_ret ret Qspec. - Proof. - iSplit; iApply specify_mono; intros this xs K; rewrite Heq; done. - Qed. + Definition lock_spec : ptr -> WpSpec mpred val val := + lock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). + + Definition unlock_spec : ptr -> WpSpec mpred val val := + unlock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). - Lemma lock_spec_equiv_lock_spec_alt - (Hlock : requirements.do_lock (Tnamed class_name) = do_lock) : - spec_type_void_to_ret "void" - (lock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ - spec_type_void_to_ret "void" lock_spec_alt. + Section equivalences. + Lemma lock_spec_equiv_lock_spec_alt this xs K : + lock_spec this xs K ⊣⊢ lock_spec_alt this xs K. Proof. - apply method_spec_equiv. intros this xs K. - unfold lock_basic_lockable. rewrite Hlock. + unfold lock_spec, lock_basic_lockable. unfold lock_spec_alt, do_lock. cbn. iSplit. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". - iExists q, (g, P), (P ** Preds.locked this (state_name g) thr qt)%I. + iExists q, (g, P), (P ** Preds.locked this g thr qt)%I. iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros "[HL HP]". iFrame. Qed. - Lemma unlock_spec_equiv_unlock_spec_alt - (Hunlock : requirements.do_unlock (Tnamed class_name) = do_unlock) : - spec_type_void_to_ret "void" (unlock_basic_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ - spec_type_void_to_ret "void" unlock_spec_alt. + Lemma unlock_spec_equiv_unlock_spec_alt this xs K : + unlock_spec this xs K ⊣⊢ unlock_spec_alt this xs K. Proof. - apply method_spec_equiv. intros this xs K. - unfold unlock_basic_lockable. rewrite Hunlock. + unfold unlock_spec, unlock_basic_lockable. unfold unlock_spec_alt, do_unlock. cbn. iSplit. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HL & HP & HK)". - iExists q, (g, P), (Preds.not_locked this (state_name g) thr qt). + iExists q, (g, P), (Preds.not_locked this g thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HL HP". iIntros "$". Qed. - Context {L : Lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)}. + Definition try_lock_spec : ptr -> WpSpec mpred val val := + try_lock_lockable cpp_ty (fun q gP => R gP.1 q gP.2). Lemma try_lock_spec_equiv_try_lock_spec_alt - (Htry_lock : requirements.do_try_lock (Tnamed class_name) = do_try_lock) : - spec_type_void_to_ret "bool" (try_lock_lockable (Tnamed class_name) (fun q gp => R gp.1 q gp.2)) ⊣⊢ - spec_type_void_to_ret "bool" try_lock_spec_alt. + (Htry_lock : requirements.do_try_lock cpp_ty = do_try_lock) this xs K : + try_lock_spec this xs K ⊣⊢ try_lock_spec_alt this xs K. Proof. - apply method_spec_equiv. intros this xs K. - unfold try_lock_lockable. rewrite Htry_lock. + unfold try_lock_spec, try_lock_lockable. rewrite Htry_lock. unfold try_lock_spec_alt, do_try_lock. cbn. iSplit. - ework with br_erefl. - iIntros "H". iDestruct "H" as (q P g thr qt) "(%Hxs & HR & #HT & HNL & HK)". iExists q, (g, P), (fun b : bool => - if b then (P ** Preds.locked this (state_name g) thr qt)%I - else Preds.not_locked this (state_name g) thr qt). + if b then (P ** Preds.locked this g thr qt)%I + else Preds.not_locked this g thr qt). iFrame "HR HK". iSplit; first done. iExists thr, qt. iFrame "HT HNL". iIntros (b) "$". @@ -201,30 +244,14 @@ End mutex_spec. (** Specialize the reusable specs to the standard mutex representation and bind them to their C++ names. *) -Module StdMutex (Preds : MUTEX_PREDS). - Module Spec := mutex_spec Preds. - - Definition gname := Preds.gname. - Definition lock_state_gname (g : gname) := g. - - Definition G := @Preds.G. - Existing Class G. - #[global] Arguments G {_ _} Σ : assert. - #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : Preds.G Σ := H. +Module StdMutex (Preds : MUTEX_PREDS with Definition cpp_ty := "std::mutex"%cpp_type). + Include Preds. + #[global] Hint Opaque token not_locked locked R : sl_opacity. - Abbreviation token := Preds.token. - Abbreviation not_locked := Preds.not_locked. - Abbreviation locked := Preds.locked. + Module Spec := mutex_spec Preds. Section with_cpp. - Context `{Σ : cpp_logic}. - - (** Fractional ownership of a <> guarding the predicate <

>. *) - Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque R : sl_opacity typeclass_instances. - #[only(cfractional,cfracvalid,ascfractional,type_ptr="std::mutex")] derive R. - #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Context `{Σ : cpp_logic, !G Σ}. (* FIXME can we delete this? *) Section with_RepFor. @@ -236,80 +263,54 @@ Module StdMutex (Preds : MUTEX_PREDS). (funI γ q P => R γ q P) := {}. End with_RepFor. - - Context `{!G Σ}. - Context `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Instance locked_learn : - Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). - Proof. solve_learnable. Qed. - cpp.spec "std::mutex::mutex()" as ctor_spec with - (\exact Reduce (Spec.ctor_spec R lock_state_gname)). + (\exact Reduce (Spec.ctor_spec)). cpp.spec "std::mutex::~mutex()" as dtor_spec with - (\exact Reduce (Spec.dtor_spec R lock_state_gname)). + (\exact Reduce (Spec.dtor_spec)). cpp.spec "std::mutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt R lock_state_gname)). + (\exact Reduce (Spec.lock_spec_alt)). cpp.spec "std::mutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt R lock_state_gname)). + (\exact Reduce (Spec.unlock_spec_alt)). cpp.spec "std::mutex::try_lock()" as try_lock_spec_alt with - (\exact Reduce (Spec.try_lock_spec_alt R lock_state_gname)). - - Definition do_lock := Spec.do_lock lock_state_gname. - #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock lock_state_gname. - #[global] Arguments do_unlock /. - Definition do_try_lock := Spec.do_try_lock lock_state_gname. - #[global] Arguments do_try_lock /. - - (** <> implements [BasicLockable] and [Lockable]. *) - Definition T : Type := gname * mpred. - - #[global] Instance mutex_basic_lockable : - BasicLockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_lock := do_lock - ; do_unlock := do_unlock }. + (\exact Reduce (Spec.try_lock_spec_alt)). cpp.spec "std::mutex::lock()" as lock_spec with - (\exact Reduce (lock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + (\exact Reduce (Spec.lock_spec)). cpp.spec "std::mutex::unlock()" as unlock_spec with - (\exact Reduce (unlock_basic_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). - - #[global] Instance mutex_lockable : - Lockable (T:=T) "std::mutex" (λ q γP, R γP.1 q γP.2) := - { do_try_lock := do_try_lock }. + (\exact Reduce (Spec.unlock_spec)). cpp.spec "std::mutex::try_lock()" as try_lock_spec with - (\exact Reduce (try_lock_lockable "std::mutex" (λ q γP, R γP.1 q γP.2))). + (\exact Reduce (Spec.try_lock_spec)). Lemma lock_spec_equiv_lock_spec_alt : lock_spec -|- lock_spec_alt. Proof. - apply (Spec.lock_spec_equiv_lock_spec_alt R lock_state_gname). - reflexivity. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.lock_spec_equiv_lock_spec_alt; done. Qed. Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. Proof. - apply (Spec.unlock_spec_equiv_unlock_spec_alt R lock_state_gname). - reflexivity. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.unlock_spec_equiv_unlock_spec_alt; done. Qed. Lemma try_lock_spec_equiv_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. Proof. - apply (Spec.try_lock_spec_equiv_try_lock_spec_alt R lock_state_gname). - reflexivity. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.try_lock_spec_equiv_try_lock_spec_alt; done. Qed. End with_cpp. End StdMutex. (** The standard-library implementation remains abstract; concrete mutex implementations supply their own [MUTEX_PREDS]. *) -Declare Module StdMutexPreds : MUTEX_PREDS. +Declare Module StdMutexPreds : MUTEX_PREDS with Definition cpp_ty := "std::mutex"%cpp_type. Module mutex := StdMutex StdMutexPreds. From a78ff0bd3de6f2197f56b60f82d4989cb4b5a2b7 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 16 Sep 2026 06:40:06 +0000 Subject: [PATCH 033/109] move thread specs to [thread/spec.v], fix [custom_mutex_hpp_proof.v] --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 7 +- rocq-brick-libstdcpp/proof/thread/spec.v | 158 ++++-- rocq-brick-libstdcpp/proof/thread/thread.hpp | 5 +- .../test/mutex/custom_mutex_hpp_proof.v | 452 ++++++++++-------- 4 files changed, 382 insertions(+), 240 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 5ec907aa..07a55b79 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -110,8 +110,11 @@ Module Type MUTEX_PREDS. { do_try_lock := do_try_lock }. (** Install [P] in an initialized mutex using its full representation and - token. The physical mutex remains initialized and unlocked. *) - Parameter install : forall (this : ptr) (old : gname) + token. The physical mutex remains initialized and unlocked. + + Initializing [this |-> R old 1$m emp] probably depends on the mutex + implementation. *) + Parameter init_R : forall (this : ptr) (old : gname) (pool : iprop.gname) (P : mpred), WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index a2b01d3c..fde75f0e 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -1,65 +1,149 @@ Require Import skylabs.bi.tls_modalities. Require Import skylabs.auto.cpp.prelude.proof. -(* Require Export skylabs.brick.libstdcpp.thread.pred. *) Require Import skylabs.brick.libstdcpp.runtime.pred. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.thread.thread_hpp. -(** Specifications for the nullary function-pointer specialization of - <>. [spawn_spec] is the constructor contract, to be bound to - the constructor specialization in the client's translation unit. The - forwarding-reference argument points to a function-pointer object; the - constructor copies its value before starting the child. - - These are normal-return contracts. They do not specify creation failures, - arbitrary callable objects, argument decay/copying, or detach. *) +(** Specs for thread creation and joining. *) Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!MutexSets.G Σ}. - (** Exclusive ownership of a thread object. [None] is non-joinable; - [Some (child, Q)] owns the right to join [child] and receive [Q], - interpreted in that child's thread context. In particular, this - predicate must not be declared persistent or fractionally splittable. *) + (** Exclusive ownership of a thread object. + [R $ None] is already joined thread and non-joinable; + [R $ Some (child, Q)] is a handle to join [child] and receive [Q]. + R would require the mpred to be WeaklyObjective. *) Parameter R : option (thread_idT * mpred) -> Rep. #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(type_ptr="std::thread")] derive R. #[global] Declare Instance R_exclusive state (this : ptr) : Exclusive0 (this |-> R state). + (** The mutex-set map tracks spawned thread IDs and supplies each child + with its [MutexSets.my_mutexes] handle. *) + Definition spawned_threads_inv (N : namespace) (γ : iprop.gname) : mpred := + inv N (Exists tids, MutexSets.mutex_set_map γ tids). + + Lemma spawned_threads_inv_alloc N : + ⊢ |={⊤}=> ∃ γ, spawned_threads_inv N γ. + Proof. + iMod MutexSets.alloc_mutex_set_map as (γ) "Hmap". + iMod (inv_alloc N _ (Exists tids, MutexSets.mutex_set_map γ tids) + with "[Hmap]") as "Hinv". + { iNext. iExists ∅. done. } + iModIntro. iExists γ. done. + Qed. + Definition entry_type : type := Tfunction (FunctionType Tvoid []). - (** The obligation is linear: resources needed by the child are consumed - at spawn. Quantification allows the runtime to choose the child's ID. - The modality prevents the parent's thread-local facts from being - silently reused in the child. *) - Definition spawn_spec : ptr -> WpSpec mpred val val := + (** Passing a function lvalue binds a function reference directly, without + an intermediate function-pointer object. The child receives its mutex + namespace pool before running the entry point. Resources that the entry + point returns can be included in [Q] and recovered by joining. *) + Definition spawn_spec_body : ptr -> WpSpec mpred val val := (\this this - \arg{fp : ptr} "f" (Vptr fp) - \prepost{q f} fp |-> primR (Tptr entry_type) q (Vptr f) + \arg{f : ptr} "f" (Vptr f) + \pre{N γ} spawned_threads_inv N γ \persist{parent} current_thread parent - \pre{Q : thread_idT -> mpred} - (∀ child, [| child <> parent |] -* - @(threadTI, child) - (wp_fptr (σ.(genv_tu).(types)) entry_type f [] - (fun _ => Q child))) + \pre{Q : mpred} [| WeaklyObjective Q |] + \pre + (∀ child, + (MutexSets.my_mutexes γ child (coPset.CoPset ⊤) -* + (* FIXME is the usage of this wp_fptr correct? *) + wp_fptr (σ.(genv_tu).(types)) entry_type f [] + (* f returns void so Q does not depend on return value *) + (fun _ => Q))) \post Exists child, - [| child <> parent |] ** this |-> R (Some (child, Q child))). + [| child <> parent |] ** this |-> R (Some (child, Q))). - (** Joining consumes the join right exactly once and leaves the object - non-joinable. Keep the child's modality on [Q]: join does not turn - child-local ownership into ownership local to the caller. *) - Definition join_spec : ptr -> WpSpec mpred val val := + (** Must be joinable, takes resource and leaves the object non-joinable. *) + Definition join_spec_body : ptr -> WpSpec mpred val val := (\this this \persist{parent} current_thread parent \pre{child Q} this |-> R (Some (child, Q)) + (* error if joining itself *) \require child <> parent - \post this |-> R None ** @(threadTI, child) Q). + \post this |-> R None ** Q). + + cpp.spec "std::thread::thread, void>(void (*&)())" as ctor_spec from source with + (\exact Reduce spawn_spec_body). + + cpp.spec "std::thread::thread, void>(void (&)())" + as ctor_ref_spec from source with + (\exact Reduce spawn_spec_body). - (** Supply the instantiated constructor name from the translation unit. *) - cpp.spec "std::thread::thread()" as ctor_spec with - (\exact Reduce spawn_spec). + (** Must be joined (and resources transferred) so can be destroyed. *) + cpp.spec "std::thread::~thread()" as dtor_spec from source with + (\this this + \pre this |-> R None + \post emp). - Definition join : mpred := - specify {| info_name := "std::thread::join()"; - info_type := tMethod "std::thread" QM "void" [] |} join_spec. + cpp.spec "std::thread::join()" as join from source with + (\exact Reduce join_spec_body). End with_cpp. + +(** Specifications for thread IDs and the current thread's operations. *) +Section thread_id_specs. + Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> + (* None if value is thread::id(), Some otherwise *) + option thread_idT -> Rep. + #[only(cfracsplittable, type_ptr="std::thread::id")] derive thread_idR. + #[global] Axiom thread_idR_WeaklyObjective : + ∀ `{Σ : cpp_logic, σ : genv} (q : cQp.t) + (o : option thread_idT) (p : ptr), + WeaklyObjective (thread_idR q o p). + #[global] Existing Instance thread_idR_WeaklyObjective. + + Context `{Σ : cpp_logic} {σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + + cpp.spec (default_ctor "std::thread::id") + as thread_id_ctor_spec from source with ( + \this this + \post this |-> thread_idR 1$m None). + + cpp.spec (const_copy_ctor "std::thread::id") + as thread_id_copy_ctor_spec from source with ( + \this this + \arg{other} "" (Vptr other) + \prepost{q o} other |-> thread_idR q o + \post this |-> thread_idR 1$m o). + + cpp.spec (dtor "std::thread::id") as thread_id_dtor_spec from source with ( + \this this + \pre{o} this |-> thread_idR 1$m o + \post emp). + + cpp.spec "std::thread::id::operator=(const std::thread::id&)" + as thread_id_copy_assign_spec from source with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{q o} other |-> thread_idR q o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::thread::id::operator=(std::thread::id&&)" + as thread_id_move_assign_spec from source with ( + \this this + \arg{other} "" (Vptr other) + \pre{old} this |-> thread_idR 1$m old + \prepost{o} other |-> thread_idR 1$m o + \post[Vref this] this |-> thread_idR 1$m o). + + cpp.spec "std::operator==(std::thread::id, std::thread::id)" + as thread_id_eq_spec from source with ( + \arg{lhs} "" (Vptr lhs) + \arg{rhs} "" (Vptr rhs) + \prepost{q1 o1} lhs |-> thread_idR q1 o1 + \prepost{q2 o2} rhs |-> thread_idR q2 o2 + \post[Vbool (bool_decide (o1 = o2))] emp). + + cpp.spec "std::this_thread::get_id()" as get_id_spec from source with ( + \persist{thr} current_thread thr + \post{result}[Vptr result] + result |-> thread_idR 1$m (Some thr)). + + cpp.spec "std::this_thread::yield()" as yield_spec from source with ( + \post emp). +End thread_id_specs. diff --git a/rocq-brick-libstdcpp/proof/thread/thread.hpp b/rocq-brick-libstdcpp/proof/thread/thread.hpp index ee6744fe..983beeca 100644 --- a/rocq-brick-libstdcpp/proof/thread/thread.hpp +++ b/rocq-brick-libstdcpp/proof/thread/thread.hpp @@ -1 +1,4 @@ -#include \ No newline at end of file +#include + +template std::thread::thread(void (*&)()); +template std::thread::thread(void (&)()); diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 475988b4..68ca44f3 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -9,60 +9,71 @@ Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.atomic.spec. Require Import skylabs.brick.libstdcpp.cassert.spec. +Require Import skylabs.brick.libstdcpp.thread.spec. NES.Open std.atomic. Import linearity. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. -(** The ghost implementation and its physical client predicates are specific - to [MyMutex]. The entire mutex-set pool still uses one shared ghost name. *) -Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) - (Owners0 : OWNER_TID) <: MUTEX_PREDS. - Record mutex_gname : Set := MkGname { +(** The concrete mutex supplies both the ghost state and the physical + predicates used by [mutex_spec]. The mutex-set pool is shared, and the + memory-order global remains a separate precondition of the C++ methods. *) +Module CustomMutexPreds <: MUTEX_PREDS. + Import MutexSets MutexTokens OwnerTid. + + Record state_gname : Set := MkStateGname { pool_gname : iprop.gname; token_gname : iprop.gname; owner_gname : iprop.gname; }. + + Definition class_name : name := "MyMutex"%cpp_name. + Definition cpp_ty : type := Tnamed class_name. + Record mutex_gname : Set := MkGname { + lock_state_gname :> state_gname; + cinv_gname : iprop.gname; + }. Definition gname : Set := mutex_gname. - Definition pool_name (γ : gname) : iprop.gname := γ.(pool_gname). + Definition pool_name (γ : gname) : iprop.gname := + γ.(lock_state_gname).(pool_gname). Definition mutex_inv_namespace : namespace := nroot .@@ "MyMutex" .@ "inv_namespace". Class stateG `{Σ : cpp_logic} := { - #[global] sets_G :: Sets0.G Σ; - #[global] tokens_G :: Tokens0.G Σ; - #[global] owners_G :: Owners0.G Σ; + #[global] sets_G :: MutexSets.G Σ; + #[global] tokens_G :: MutexTokens.G Σ; + #[global] owners_G :: OwnerTid.G Σ; }. Definition G := @stateG. Existing Class G. #[global] Arguments G {_ _} Σ : assert. #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. - Definition token `{Σ : cpp_logic, !G Σ} - (γ : gname) (q : cQp.t) : mpred := - Tokens0.token γ.(token_gname) q. + Definition state_token `{Σ : cpp_logic, !G Σ} + (γ : state_gname) (q : cQp.t) : mpred := + MutexTokens.token γ.(token_gname) q. Definition not_locked_ghost `{Σ : cpp_logic, !G Σ} - (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := - Sets0.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** - Tokens0.token γ.(token_gname) q. + (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := + MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** + MutexTokens.token γ.(token_gname) q. Definition owner_token `{Σ : cpp_logic, !G Σ} - (γ : mutex_gname) (th : thread_idT) (q : Qp) : mpred := - Tokens0.given_token γ.(token_gname) q ** - Owners0.owner_tid_frag γ.(owner_gname) (Some th). + (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := + MutexTokens.given_token γ.(token_gname) q ** + OwnerTid.owner_tid_frag γ.(owner_gname) (Some th). - #[global] Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). + #[global] Instance state_token_fractional + `{Σ : cpp_logic, !G Σ} γ : CFractional (state_token γ). Proof. - intros q1 q2. rewrite /token cQp.frac_add. - apply Tokens0.token_fractional. + intros q1 q2. rewrite /state_token cQp.frac_add. + apply MutexTokens.token_fractional. Qed. - #[global] Instance token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). - Proof. rewrite /token. apply _. Qed. + #[global] Instance state_token_timeless + `{Σ : cpp_logic, !G Σ} γ q : Timeless (state_token γ q). + Proof. rewrite /state_token. apply _. Qed. #[global] Instance owner_token_timeless `{Σ : cpp_logic, !G Σ} γ th q : Timeless (owner_token γ th q). @@ -79,15 +90,15 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) and the token balance. The thread retains its remaining mutex names. When free, both halves of the previous owner remain in the invariant. *) Definition state `{Σ : cpp_logic, !G Σ} - (γ : mutex_gname) (b : bool) : mpred := + (γ : state_gname) (b : bool) : mpred := (if b then - ∃ th, Owners0.owner_tid_auth γ.(owner_gname) (Some th) ** - Sets0.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** - Tokens0.token_not_full γ.(token_gname) + ∃ th, OwnerTid.owner_tid_auth γ.(owner_gname) (Some th) ** + MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** + MutexTokens.token_not_full γ.(token_gname) else - ∃ owner, Owners0.owner_tid_auth γ.(owner_gname) owner ** - Owners0.owner_tid_frag γ.(owner_gname) owner ** - Tokens0.token_full γ.(token_gname))%I. + ∃ owner, OwnerTid.owner_tid_auth γ.(owner_gname) owner ** + OwnerTid.owner_tid_frag γ.(owner_gname) owner ** + MutexTokens.token_full γ.(token_gname))%I. #[global] Instance state_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ b : @@ -97,33 +108,33 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Section state_laws. Context `{Σ : cpp_logic, !G Σ}. - Lemma alloc (γpool : iprop.gname) : + Lemma alloc_state (γpool : iprop.gname) : ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** - token γ 1$m ** state γ false. + state_token γ 1$m ** state γ false. Proof. - iMod Tokens0.alloc as (gt) "[T GT]". - iMod (Owners0.alloc None) as (go) "[OA OF]". - iModIntro. iExists (MkGname γpool gt go). + iMod MutexTokens.alloc as (gt) "[T GT]". + iMod (OwnerTid.alloc None) as (go) "[OA OF]". + iModIntro. iExists (MkStateGname γpool gt go). iSplit; first done. - rewrite /token /state /=. iFrame "T". iExists None. + rewrite /state_token /state /=. iFrame "T". iExists None. iFrame "OA OF". - iApply Tokens0.token_full_init. iExact "GT". + iApply MutexTokens.token_full_init. iExact "GT". Qed. - Lemma do_lock γ th q : + Lemma state_lock γ th q : state γ false ** not_locked_ghost γ th q |-- (|==> state γ true ** owner_token γ th q). Proof. rewrite /state /not_locked_ghost /owner_token. iIntros "[State [Sets T]]". iDestruct "State" as (owner) "(OA & OF & Balance)". - iDestruct (Tokens0.acquire with "[$Balance $T]") as "[GT Balance]". - iMod (Owners0.owner_update γ.(owner_gname) _ _ (Some th) + iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". + iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) with "[$OA $OF]") as "[OA OF]". iModIntro. iFrame "GT OF". iExists th. iFrame. Qed. - Lemma do_unlock γ th q : + Lemma state_unlock γ th q : state γ true ** owner_token γ th q |-- (|==> state γ false ** not_locked_ghost γ th q). Proof. @@ -132,7 +143,7 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. injection Heq as ->. - iDestruct (Tokens0.release with "[$Balance $GT]") as "[T Balance]". + iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". iModIntro. iFrame "Sets T". iExists (Some th). iFrame. Qed. @@ -141,224 +152,261 @@ Module CustomMutexState (Sets0 : MUTEX_SETS) (Tokens0 : MUTEX_TOKENS) Proof. rewrite /state /owner_token. iIntros "[State [_ OF]]". iDestruct "State" as (owner) "(_ & OF0 & _)". - iDestruct (Owners0.owner_tid_frag_exclusive with "OF0 OF") as %[]. + iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. Lemma locked_full_token γ : - state γ true ** token γ 1$m |-- False. + state γ true ** state_token γ 1$m |-- False. Proof. - rewrite /state /token. iIntros "[State T]". + rewrite /state /state_token. iIntros "[State T]". iDestruct "State" as (th) "(_ & _ & Balance)". - iApply (Tokens0.token_not_full_full_token with "[$Balance $T]"). + iApply (MutexTokens.token_not_full_full_token with "[$Balance $T]"). Qed. End state_laws. - #[global] Hint Opaque token not_locked_ghost owner_token state : sl_opacity typeclass_instances. - - Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> - (* None if value is thread::id(), Some otherwise *) - option thread_idT -> Rep. - #[only(cfracsplittable, type_ptr="std::thread::id")] derive thread_idR. - #[global] Axiom thread_idR_WeaklyObjective : - ∀ `{Σ : cpp_logic, σ : genv} (q : cQp.t) - (o : option thread_idT) (p : ptr), - WeaklyObjective (thread_idR q o p). - #[global] Existing Instance thread_idR_WeaklyObjective. + #[global] Hint Opaque state_token not_locked_ghost owner_token state : sl_opacity typeclass_instances. Definition globals `{Σ : cpp_logic} {σ : genv} (q : cQp.t) : mpred := _global "std::memory_order_seq_cst" |-> primR "enum std::memory_order" q (memory_order.to_val memory_order.seq_cst). + #[global] Arguments globals /. + + Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := + state_token γ q. Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := - not_locked_ghost γ th q ** globals q. + not_locked_ghost γ th q. + Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := + this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m (Some th) ** + owner_token γ th q. + #[global] Hint Opaque token : sl_opacity typeclass_instances. + #[global] Hint Opaque locked : typeclass_instances. #[global] Arguments not_locked /. - #[global] Arguments globals /. + #[global] Arguments locked /. + #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : + CFractional (token γ). + Proof. rewrite /token. apply state_token_fractional. Qed. + #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : + Timeless (token γ q). + Proof. rewrite /token. apply _. Qed. #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (not_locked this γ th q). - Proof. rewrite /not_locked /not_locked_ghost /globals. apply _. Qed. + Proof. rewrite /not_locked /not_locked_ghost. apply _. Qed. #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th : Exclusive1 (not_locked this γ th). Proof. intros q1 q2. rewrite /not_locked /not_locked_ghost. - iIntros "[[F1 _] _] [[F2 _] _]". + iIntros "[F1 _] [F2 _]". have Hnonempty : (↑ mutex_inv_namespace : coPset) ∩ ↑ mutex_inv_namespace ≠ ∅. { rewrite intersection_idemp_L. apply nclose_non_empty. } - iDestruct (Sets0.my_mutexes_exclusive γ.(pool_gname) th + iDestruct (MutexSets.my_mutexes_exclusive _ th (↑ mutex_inv_namespace) (↑ mutex_inv_namespace) Hnonempty with "[$F1 $F2]") as %[]. Qed. - - (** Public ownership includes the physical owner written by [lock]. *) - Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} - (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := - globals q ** - (this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m (Some th) ** - owner_token γ th q). - #[global] Arguments locked /. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). - Proof. rewrite /locked /globals. apply _. Qed. + Proof. rewrite /locked. apply _. Qed. #[global] Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). - Proof. - intros th1 th2 q1 q2. rewrite /locked. - apply observe_2_sep_r. apply observe_2_sep_r. apply _. - Qed. - -End CustomMutexState. - -Module LockState := CustomMutexState MutexSets MutexTokens OwnerTid. - -(** Verify the implementation using its ghost resources and public state predicates. *) -Module custom_mutex. - Module State := LockState. - Module Spec := mutex_spec State. - - Abbreviation N := "MyMutex"%cpp_name. - Abbreviation thread_idR := LockState.thread_idR. - #[local] Hint Opaque LockState.thread_idR : sl_opacity typeclass_instances. + Proof. intros th1 th2 q1 q2. rewrite /locked. apply observe_2_sep_r. apply _. Qed. #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. - (* thread::id operations and yield() are not proved for now. *) - Section unproved_specs. - Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ}. - Context `{MOD : source ⊧ σ}. - - cpp.spec (default_ctor "std::thread::id") as thread_id_ctor_spec with ( - \this this - \post this |-> thread_idR 1$m None). - - cpp.spec (const_copy_ctor "std::thread::id") as thread_id_copy_ctor_spec with ( - \this this - \arg{other} "" (Vptr other) - \prepost{q o} other |-> thread_idR q o - \post this |-> thread_idR 1$m o). - - cpp.spec (dtor "std::thread::id") as thread_id_dtor_spec with ( - \this this - \pre{o} this |-> thread_idR 1$m o - \post emp). - - cpp.spec "std::thread::id::operator=(const std::thread::id&)" - as thread_id_copy_assign_spec with ( - \this this - \arg{other} "" (Vptr other) - \pre{old} this |-> thread_idR 1$m old - \prepost{q o} other |-> thread_idR q o - \post[Vref this] this |-> thread_idR 1$m o). - - cpp.spec "std::thread::id::operator=(std::thread::id&&)" - as thread_id_move_assign_spec with ( - \this this - \arg{other} "" (Vptr other) - \pre{old} this |-> thread_idR 1$m old - \prepost{o} other |-> thread_idR 1$m o - \post[Vref this] this |-> thread_idR 1$m o). - - cpp.spec "std::operator==(std::thread::id, std::thread::id)" - as thread_id_eq_spec with ( - \arg{lhs} "" (Vptr lhs) - \arg{rhs} "" (Vptr rhs) - \prepost{q1 o1} lhs |-> thread_idR q1 o1 - \prepost{q2 o2} rhs |-> thread_idR q2 o2 - \post[Vbool (bool_decide (o1 = o2))] emp). - - cpp.spec "std::this_thread::get_id()" as get_id_spec with ( - \persist{thr} current_thread thr - \post{result}[Vptr result] - result |-> thread_idR 1$m (Some thr)). - - cpp.spec "std::this_thread::yield()" as yield_spec with ( - \post emp). - End unproved_specs. - - Record gname : Set := MkGname { - lock_state_gname :> State.mutex_gname; - cinv_gname : iprop.gname; - }. - Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, - !State.G Σ}. + Context `{Σ : cpp_logic, !G Σ}. (** The physical lock bit agrees with the abstract ghost state. The protected resources and cleared owner field are available while free. *) - Definition mutex_inv (this : ptr) (γ : gname) (P : mpred) : mpred := + Definition mutex_inv {σ : genv} (this : ptr) (γ : gname) (P : mpred) : mpred := ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** - State.state γ b ** + state γ b ** if b then emp else P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None. - Definition IR (γ : gname) (q : cQp.t) (P : mpred) : Rep := - structR N q$m ** + Definition R {HAS_THREADS : HasStdThreads Σ} {σ : genv} (γ : gname) (q : cQp.t) (P : mpred) : Rep := + structR class_name q$m ** as_Rep (fun this => - cinv State.mutex_inv_namespace (cinv_gname γ) (mutex_inv this γ P) ** + cinv mutex_inv_namespace (cinv_gname γ) (mutex_inv this γ P) ** cinv_own (cinv_gname γ) q ). - Hint Opaque IR : sl_opacity typeclass_instances. - #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive IR. + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive R. + + #[global] Instance R_learnable {HAS_THREADS : HasStdThreads Σ} {σ : genv} : + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + + Context {σ : genv} {HAS_THREADS : HasStdThreads Σ}. + + #[global] Instance locked_learn : + Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). + Proof. solve_learnable. Qed. + + Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + (locked this lk.1 thr qt ** lk.2 -* K). + #[global] Arguments do_lock /. + + Definition do_unlock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** locked this lk.1 thr qt ** ▷lk.2 ** + (not_locked this lk.1 thr qt -* K). + #[global] Arguments do_unlock /. + + #[global] Instance mutex_basic_lockable : + BasicLockable (T := gname * mpred) cpp_ty + (fun q gP => R gP.1 q gP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. + + Definition do_try_lock (this : ptr) (lk : gname * mpred) + (K : bool -> mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + ∀ b : bool, + (if b then lk.2 ** locked this lk.1 thr qt + else not_locked this lk.1 thr qt) -* K b. + #[global] Arguments do_try_lock /. + + #[global] Instance mutex_lockable : + Lockable (T := gname * mpred) cpp_ty + (fun q gP => R gP.1 q gP.2) := + { do_try_lock := do_try_lock }. + + Lemma init_R (this : ptr) (old : gname) + (pool : iprop.gname) (P : mpred) : + WeaklyObjective P -> + this |-> R old 1$m emp ** token old 1$m ** ▷P |-- + (|={⊤}=> ∃ g, this |-> R g 1$m P ** token g 1$m). + Proof. + intros HP. iIntros "(HR & T & P)". + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(S & #CI & CO)". + iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. + iMod (alloc_state pool) as (gs) "(_ & Tnew & Stnew)". + iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. + - iModIntro. iExists (MkGname gs gi). + rewrite /R _at_sep _at_as_Rep /token /mutex_inv /=. + iFrame "CInew S COnew Tnew". + - iNext. rewrite /mutex_inv. + iDestruct "Inv" as (b) "(L & St & Resources)". + destruct b. + + iDestruct (locked_full_token with "[$St $T]") as %[]. + + iDestruct "Resources" as "[_ Owner]". + iExists false. iFrame "L Stnew P Owner". + iApply (affine with "[St T]"); last iAccu. apply mpred_BiAffine. + - apply _. + Qed. + + Lemma register_thread + (this : ptr) (g : gname) (q : cQp.t) (P : mpred) + (th : thread_idT) (qt : cQp.t) : + this |-> R g q P ** token g qt ** + MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace) |-- + this |-> R g q P ** not_locked this g th qt. + Proof. + rewrite /not_locked /not_locked_ghost /token /state_token /pool_name. + iIntros "($ & T & M)". iFrame. + Qed. + End with_Σ. +End CustomMutexPreds. + +(** Verify the implementation using its ghost resources and public state predicates. *) +Module custom_mutex. + Import CustomMutexPreds. + Module Spec := mutex_spec CustomMutexPreds. + + Abbreviation N := "MyMutex"%cpp_name. + #[local] Hint Opaque thread_idR : sl_opacity typeclass_instances. + + #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} + (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : + WeaklyObjective (p |-> R). + Proof. rewrite INTERNAL._at_eq. apply _. Qed. + + Abbreviation IR := CustomMutexPreds.R. + + Section with_Σ. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, + !CustomMutexPreds.G Σ}. Context `{MOD : source ⊧ σ}. + (* FIXME any reason to keep Reduce? *) cpp.spec "MyMutex::MyMutex()" as ctor_spec with - (\exact Reduce (Spec.ctor_spec IR lock_state_gname)). + (\exact Reduce (Spec.ctor_spec)). cpp.spec "MyMutex::~MyMutex()" as dtor_spec with - (\exact Reduce (Spec.dtor_spec IR lock_state_gname)). + (\exact Reduce (Spec.dtor_spec)). Definition T : Type := gname * mpred. cpp.spec "MyMutex::do_lock()" as do_lock_spec with ( \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{(qt : cQp.t)} State.not_locked_ghost g thr qt ** State.globals qt - \post P ** State.globals qt ** + \prepost{qg} globals qg + \pre{(qt : Qp)} not_locked_ghost g thr qt + \post P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - State.owner_token g thr qt). + owner_token g thr qt). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr - \pre{qt} State.globals qt ** - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** - State.owner_token g thr qt + \prepost{qg} globals qg + \pre{qt} this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + owner_token g thr qt \pre ▷P - \post State.not_locked_ghost g thr qt ** State.globals qt). - - Definition do_lock := Spec.do_lock lock_state_gname. - #[global] Arguments do_lock /. - Definition do_unlock := Spec.do_unlock lock_state_gname. - #[global] Arguments do_unlock /. - - #[global] Instance custom_mutex_basic_lockable : - BasicLockable (T := T) (Tnamed N) - (fun q gP => IR gP.1 q gP.2) := - { do_lock := do_lock - ; do_unlock := do_unlock }. + \post not_locked_ghost g thr qt). cpp.spec "MyMutex::lock()" as lock_spec_alt with - (\exact Reduce (Spec.lock_spec_alt IR lock_state_gname)). + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.lock_spec_alt this)). cpp.spec "MyMutex::unlock()" as unlock_spec_alt with - (\exact Reduce (Spec.unlock_spec_alt IR lock_state_gname)). + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.unlock_spec_alt this)). cpp.spec "MyMutex::lock()" as lock_spec with - (\exact Reduce - (lock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + (\this this + \prepost{qg} globals qg + \exact Reduce + (Spec.lock_spec this)). cpp.spec "MyMutex::unlock()" as unlock_spec with - (\exact Reduce - (unlock_basic_lockable (Tnamed N) (fun q gP => IR gP.1 q gP.2))). + (\this this + \prepost{qg} globals qg + \exact Reduce + (Spec.unlock_spec this)). + + Lemma lock_spec_equiv_lock_spec_alt : lock_spec ⊣⊢ lock_spec_alt. + Proof. + rewrite /lock_spec /lock_spec_alt /specify. + do 2 f_equiv. intros this xs K. + rewrite !add_with_equiv. + f_equiv=> qg. + by rewrite !add_prepost_equiv Spec.lock_spec_equiv_lock_spec_alt. + Qed. + + Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec ⊣⊢ unlock_spec_alt. + Proof. + rewrite /unlock_spec /unlock_spec_alt /specify. + do 2 f_equiv. intros this xs K. + rewrite !add_with_equiv. + f_equiv=> qg. + by rewrite !add_prepost_equiv Spec.unlock_spec_equiv_unlock_spec_alt. + Qed. Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). @@ -372,13 +420,13 @@ Module custom_mutex. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P - \consuming{qt} State.not_locked_ghost g thr qt + \consuming{qt} not_locked_ghost g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** State.owner_token g thr qt ** + if bool_decide (res = 0) then P ** owner_token g thr qt ** p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - else State.not_locked_ghost g thr qt) + else not_locked_ghost g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -388,7 +436,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_exchange. iAuIntro1. rewrite /atomic1_acc. - iInv State.mutex_inv_namespace as "Inv" "Hclose". + iInv mutex_inv_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -412,7 +460,7 @@ Module custom_mutex. iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. iPureIntro. auto. + iDestruct "Resources" as "[P Owner]". - iMod (State.do_lock _ with "[$State $NL]") as "[State Locked]". + iMod (state_lock _ with "[$State $NL]") as "[State Locked]". iMod ("Hclose" with "[L State]") as "_". { iNext. rewrite /mutex_inv. iExists true. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -430,10 +478,10 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None - \consuming{qt} State.owner_token g thr qt + \consuming{qt} owner_token g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K - \instantiate K := (p |-> IR g q P ** State.not_locked_ghost g thr qt) + \instantiate K := (p |-> IR g q P ** not_locked_ghost g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -443,7 +491,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc /=. - iInv State.mutex_inv_namespace as "Inv" "Hclose". + iInv mutex_inv_namespace as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -452,7 +500,7 @@ Module custom_mutex. iSplitL "L"; first by ework $usenamed=true with br_erefl. iNext. iIntros "L". iMod "Y" as "_". destruct b. - - iMod (State.do_unlock _ with "[$State $Locked]") as "[State NL]". + - iMod (state_unlock _ with "[$State $Locked]") as "[State NL]". iMod ("Hclose" with "[L State P Owner]") as "_". { iNext. rewrite /mutex_inv. iExists false. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -460,7 +508,7 @@ Module custom_mutex. iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. - - iDestruct (State.unlocked_owner_token with "[$State $Locked]") as %[]. + - iDestruct (unlocked_owner_token with "[$State $Locked]") as %[]. Qed. Hint Resolve do_store_C : sl_opacity. @@ -498,9 +546,13 @@ Module custom_mutex. verify_spec; go. Qed. + #[local] Remove Hints CustomMutexPreds.locked_learn : typeclass_instances. + Lemma mymutex_lock_alt_proof : verify[source] lock_spec_alt. Proof using MOD HAS_THREADS. verify_spec; ego. + wname [CustomMutexPreds.not_locked_ghost] "NL". + iFrame "NL". ego. Unshelve. all: exact (1$m)%cQp. Qed. @@ -508,6 +560,8 @@ Module custom_mutex. Proof using MOD HAS_THREADS. verify_spec. repeat (go; ework). + wname [CustomMutexPreds.owner_token] "Owner". + iFrame "Owner". ego. Unshelve. all: exact (1$m)%cQp. Qed. @@ -518,7 +572,7 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_idR _ _] "Owner". - iMod (State.alloc 1%positive) as (gs) "(_ & T & State)". + iMod (alloc_state 1%positive) as (gs) "(_ & T & State)". iMod (cinv_alloc with "[L P Owner State]") as (gi) "[#CI CO]"; last first. - iModIntro. iExists (MkGname gs gi). @@ -535,13 +589,13 @@ Module custom_mutex. work. wname [cinv] "#CI". wname [cinv_own] "CO". - wname [State.token] "T". + wname [CustomMutexPreds.token] "T". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. go. iDestruct "Inv" as (b) "(Lock & State & Resources)". destruct b. - - iDestruct (State.locked_full_token with "[$State $T]") as %[]. + - iDestruct (locked_full_token with "[$State $T]") as %[]. - iDestruct "Resources" as "[P Owner]". iAssert emp with "[State T]" as "_". { iApply (affine with "[State T]"); last iAccu. apply mpred_BiAffine. } @@ -550,15 +604,13 @@ Module custom_mutex. Lemma mymutex_lock_proof : verify[source] lock_spec. Proof using MOD HAS_THREADS. - have -> : lock_spec ⊣⊢ lock_spec_alt. - { apply (Spec.lock_spec_equiv_lock_spec_alt IR lock_state_gname). done. } + rewrite lock_spec_equiv_lock_spec_alt. exact mymutex_lock_alt_proof. Qed. Lemma mymutex_unlock_proof : verify[source] unlock_spec. Proof using MOD HAS_THREADS. - have -> : unlock_spec ⊣⊢ unlock_spec_alt. - { apply (Spec.unlock_spec_equiv_unlock_spec_alt IR lock_state_gname). done. } + rewrite unlock_spec_equiv_unlock_spec_alt. exact mymutex_unlock_alt_proof. Qed. From 8da0951377ad01a84a39ceb584f5e9112cb16205 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 17 Sep 2026 05:22:27 +0000 Subject: [PATCH 034/109] add double_incr example and proof from paper as a mutex client; add std::thread specs --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 5 + rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 1 + rocq-brick-libstdcpp/proof/thread/spec.v | 5 + .../test/mutex/custom_mutex_hpp_proof.v | 6 +- .../test/mutex/double_incr.cpp | 6 +- .../test/mutex/double_incr_cpp_proof.v | 189 ++++++++++++++++++ .../test/mutex/double_incr_ghost.v | 93 +++++++++ 7 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v create mode 100644 rocq-brick-libstdcpp/test/mutex/double_incr_ghost.v diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 3739c0ba..2bc8f9dc 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -38,6 +38,8 @@ Module Type MUTEX_SETS. `{Σ : cpp_logic, !G Σ} γ M : WeaklyObjective (mutex_set_map γ M). #[global] Declare Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th E : WeaklyObjective (my_mutexes γ th E). + #[global] Declare Instance my_mutexes_objective + `{Σ : cpp_logic, !G Σ} γ th E : Objective (my_mutexes γ th E). Parameter my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), E1 ∩ E2 <> ∅ -> @@ -199,6 +201,9 @@ Module MutexSets : MUTEX_SETS. #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th E : WeaklyObjective (my_mutexes γ th E). Proof. rewrite /my_mutexes. apply _. Qed. + #[global] Instance my_mutexes_objective `{Σ : cpp_logic, !G Σ} γ th E : + Objective (my_mutexes γ th E). + Proof. rewrite /my_mutexes. apply _. Qed. Section theory. Context `{Σ : cpp_logic, !G Σ}. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 07a55b79..27860bbb 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -119,6 +119,7 @@ Module Type MUTEX_PREDS. WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- (|={⊤}=> ∃ g, + [| pool_name g = pool |] ** this |-> R g 1$m P ** token g 1$m). (** Register a thread by consuming its handle for this mutex's namespace diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index fde75f0e..47caefca 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -37,6 +37,11 @@ Section with_cpp. Definition entry_type : type := Tfunction (FunctionType Tvoid []). + #[global] Declare Instance wp_fptr_objective (f : ptr) (Q : mpred) + `{!WeaklyObjective Q} : + ObjectiveWith threadTI + (wp_fptr (σ.(genv_tu).(types)) entry_type f [] (fun _ => Q)). + (** Passing a function lvalue binds a function reference directly, without an intermediate function-pointer object. The child receives its mutex namespace pool before running the entry point. Resources that the entry diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 68ca44f3..c186f8d8 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -284,15 +284,17 @@ Module CustomMutexPreds <: MUTEX_PREDS. (pool : iprop.gname) (P : mpred) : WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- - (|={⊤}=> ∃ g, this |-> R g 1$m P ** token g 1$m). + (|={⊤}=> ∃ g, [| pool_name g = pool |] ** + this |-> R g 1$m P ** token g 1$m). Proof. intros HP. iIntros "(HR & T & P)". iEval (rewrite /R _at_sep _at_as_Rep) in "HR". iDestruct "HR" as "(S & #CI & CO)". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. - iMod (alloc_state pool) as (gs) "(_ & Tnew & Stnew)". + iMod (alloc_state pool) as (gs) "(%Hpool & Tnew & Stnew)". iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. - iModIntro. iExists (MkGname gs gi). + iSplit; first (iPureIntro; exact Hpool). rewrite /R _at_sep _at_as_Rep /token /mutex_inv /=. iFrame "CInew S COnew Tnew". - iNext. rewrite /mutex_inv. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr.cpp b/rocq-brick-libstdcpp/test/mutex/double_incr.cpp index 0d7a822f..8a909bc7 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr.cpp +++ b/rocq-brick-libstdcpp/test/mutex/double_incr.cpp @@ -2,8 +2,8 @@ #include #include -unsigned int x = 0; -std::mutex m; +unsigned int x{0}; +std::mutex m{}; void double_incr() { m.lock(); @@ -19,6 +19,6 @@ int main() { t1.join(); t2.join(); - assert(x == 4); + assert(x % 2 == 0); return 0; } diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v new file mode 100644 index 00000000..d22a4a25 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -0,0 +1,189 @@ +Require Import skylabs.auto.cpp.proof. +Require Import skylabs.bi.tls_modalities. +Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.thread.spec. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Require Import skylabs.brick.libstdcpp.cassert.spec. +Require Import skylabs.brick.libstdcpp.test.mutex.double_incr_cpp. +Require Import skylabs.brick.libstdcpp.test.mutex.double_incr_ghost. + +#[local] Hint Opaque double_incr_protocol.frac double_incr_protocol.protected + double_incr_protocol.done double_incr_protocol.evenR : sl_opacity. + +Section with_cpp. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!mutex.G Σ, !double_incr_protocol.G Σ}. + + cpp.spec "double_incr()" as double_incr_spec_body from source with ( + \prepost{g q γ} _global "m" |-> mutex.R g q + (double_incr_protocol.protected γ (_global "x")) + \persist{thr} current_thread thr + \prepost{qt} mutex.not_locked (_global "m") g thr qt + \pre double_incr_protocol.frac γ (1 / 2) + \post double_incr_protocol.done γ (_global "x")). + + Lemma double_incr_ok : verify[source] "double_incr()". + Proof. + verify_spec; go. + iExists q, (mutex.locked (_global "m") g thr qt ** + double_incr_protocol.protected γ (_global "x"))%I, qt. + iFrame. iSplitR; first (iIntros "$"). + iIntros "[? [? ?]]". + iDestruct (double_incr_protocol.prepare with "[$]") as "[Hx Hfinish]". + iDestruct "Hx" as (n) "[? %Heven]". + go. + replace (n + 1 + 1)%Z with (n + 2)%Z by lia. + iDestruct select (_global "x" |-> uintR _ _) as "Hx". + iAssert (double_incr_protocol.evenR (_global "x")) with "[Hx]" as "Hx". + { iExists (trim 32 (n + 2)). iFrame. iPureIntro. + apply double_incr_protocol.trim_add_two_even. exact Heven. } + iDestruct ("Hfinish" with "Hx") as "[? ?]". + iExists q, (mutex.not_locked (_global "m") g thr qt), qt; go. + Qed. + + (** The verification below is conditional on these objectivity properties + of the thread model. They are explicit proof hypotheses. *) + Context `{worker_spec_objective : !ObjectiveWith threadTI double_incr_spec_body}. + Context `{even_weakly_objective : + !WeaklyObjective (double_incr_protocol.evenR (_global "x"))}. + Context `{shared_mutex_objective : ∀ g γ, + ObjectiveWith threadTI (_global "m" |-> mutex.R g (1 / 2)$m + (double_incr_protocol.protected γ (_global "x")))}. + Context `{shared_token_objective : ∀ g, + ObjectiveWith threadTI (mutex.token g (1 / 2)$m)}. + + #[local] Instance protected_weakly_objective γ : + WeaklyObjective (double_incr_protocol.protected γ (_global "x")). + Proof using Type even_weakly_objective. + rewrite /double_incr_protocol.protected /double_incr_protocol.auth + /double_incr_protocol.frac. apply _. + Qed. + + (** Each worker receives half of the mutex ownership and a completion + ticket. Its namespace handle comes from spawn. *) + Definition worker_start (g : mutex.gname) + (γ : double_incr_protocol.gname) : mpred := + _global "m" |-> mutex.R g (1 / 2)$m + (double_incr_protocol.protected γ (_global "x")) ** + mutex.token g (1 / 2)$m ** double_incr_protocol.frac γ (1 / 2). + + #[local] Instance worker_start_objective g γ : + ObjectiveWith threadTI (worker_start g γ). + Proof using Type shared_mutex_objective shared_token_objective. + rewrite /worker_start /double_incr_protocol.frac. + apply sep_objective_with; first apply _. + apply sep_objective_with; first apply _. + apply objective_objective_with. apply _. + Qed. + + #[local] Instance done_weakly_objective γ : + WeaklyObjective (double_incr_protocol.done γ (_global "x")). + Proof using Type even_weakly_objective. + rewrite /double_incr_protocol.done /double_incr_protocol.auth. apply _. + Qed. + + (** Register the child's mutex namespace, then invoke the verified worker + contract in that child's thread context. *) + Lemma worker_entry `{MOD : source ⊧ σ} g γ : + double_incr_spec_body ** worker_start g γ |-- + ∀ child, + MutexSets.my_mutexes (mutex.pool_name g) child (coPset.CoPset ⊤) -* + wp_fptr (σ.(genv_tu).(types)) thread.spec.entry_type + (_global "double_incr()") [] + (fun _ => double_incr_protocol.done γ (_global "x")). + Proof using Type worker_spec_objective even_weakly_objective + shared_mutex_objective shared_token_objective. + iIntros "[#Hcode Hstart]" (child) "Hset". + rewrite (objective_with_intro_exactly_at threadTI + (wp_fptr (σ.(genv_tu).(types)) thread.spec.entry_type + (_global "double_incr()") [] + (fun _ => double_incr_protocol.done γ (_global "x"))) child). + iDestruct (monPred_atleast_exactly_at threadTI child) as "HT". + iModIntro. + iDestruct "Hstart" as "(Hm & Htoken & Hfrac)". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name (mutex.pool_name g) + child ⊤ (↑mutex.mutex_inv_namespace) ltac:(set_solver) + with "Hset") as "[Hrest Hname]". + iDestruct (mutex.register_thread with "[$Hm $Htoken $Hname]") + as "[Hm Hnotlocked]". + iEval (unfold double_incr_spec_body, specify, unmaterialized_specR) in "Hcode". + iApply (invoke.use_cptrR with "Hcode"). cbn. + iSplitR; first done. + iExists g, ((1 / 2)$m)%cQp, γ, child, ((1 / 2)$m)%cQp. + iSplitR; first done. iFrame. + iFrame "HT". + iIntros "[HR [HNL HD]]" (v) "HV". iExact "HD". + Qed. + + #[local] Hint Opaque worker_start thread.spec.spawned_threads_inv : sl_opacity. + + Definition thread_pool_namespace : namespace := + nroot .@@ "double_incr" .@ "threads". + + (** Global vars are given as preconditions. + Assume the mutex is initialized with an empty invariant (assocaited with + γold), which can later be updated to a new invariant with init_R. *) + cpp.spec "main()" as main_spec from source with ( + \pre _global "x" |-> uintR 1$m 0 + \pre{γold} _global "m" |-> mutex.R γold 1$m emp ** mutex.token γold 1$m + \post[Vint 0] double_incr_protocol.evenR (_global "x")). + + Lemma main_ok : verify[source] "main()". + Proof using Type worker_spec_objective even_weakly_objective + shared_mutex_objective shared_token_objective. + verify_spec. + let rec expose_globals := + first [iDestruct select (_global "x" |-> uintR 1$m 0) as "Hx" + | progress go1; expose_globals] in + expose_globals. + iAssert (double_incr_protocol.evenR (_global "x")) with "[Hx]" as "Hx". + { iExists 0. iFrame. done. } + iMod (double_incr_protocol.alloc with "Hx") as (γ) "(HP & Hf1 & Hf2)". + iDestruct select (_global "m" |-> mutex.R γold 1$m emp) as "Hm". + iDestruct select (mutex.token γold 1$m) as "Htoken". + iMod (thread.spec.spawned_threads_inv_alloc thread_pool_namespace) + as (pool) "#Hpool". + (* Logical invariant setup for the already initialized mutex. *) + iMod (mutex.init_R (_global "m") γold pool + (double_incr_protocol.protected γ (_global "x")) ltac:(apply _) + with "[Hm Htoken HP]") as (g) "(%Hpool_name & Hm & Htoken)". + { iFrame "Hm Htoken". iNext. done. } + subst pool. + iDestruct "Hm" as "[Hm1 Hm2]". + iEval (rewrite (cfractional_split_half (mutex.token g)) + cQp.scale_mut Qp.mul_1_r) in "Htoken". + iDestruct "Htoken" as "[Ht1 Ht2]". + iAssert (worker_start g γ ** worker_start g γ)%I + with "[Hm1 Hm2 Ht1 Ht2 Hf1 Hf2]" as "[Hstart1 Hstart2]". + { rewrite /worker_start. iFrame. } + go. + iDestruct select double_incr_spec_body as "#Hcode". + iExists thread_pool_namespace, (mutex.pool_name g), + (double_incr_protocol.done γ (_global "x")). + iFrame "#". iSplitR; first (iPureIntro; apply _). + iSplitL "Hstart1". + - iApply worker_entry. iFrame "Hcode Hstart1". + - iIntros "Hthread1". + iDestruct "Hthread1" as (child1) "[% HR1]". + iDestruct (observe (type_ptr "std::thread" t1_addr) with "HR1") as "#?". + iDestruct "HR1" as "?". go. + iExists thread_pool_namespace, (mutex.pool_name g), + (double_incr_protocol.done γ (_global "x")). + iFrame "#". iSplitR; first (iPureIntro; apply _). + iSplitL "Hstart2". + + iApply worker_entry. iFrame "Hcode Hstart2". + + iIntros "Hthread2". + iDestruct "Hthread2" as (child2) "[% HR2]". + iDestruct (observe (type_ptr "std::thread" t2_addr) with "HR2") as "#?". + iDestruct "HR2" as "?". go. + iExists child1, (double_incr_protocol.done γ (_global "x")). go. + iExists child2, (double_incr_protocol.done γ (_global "x")). go. + iDestruct (double_incr_protocol.join with "[$]") + as (n) "[Hx %Heven]". + go. + iExists (1$m)%cQp; go. + iFrame. + iExists n. iFrame. done. + Qed. +End with_cpp. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_ghost.v b/rocq-brick-libstdcpp/test/mutex/double_incr_ghost.v new file mode 100644 index 00000000..8cd82526 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_ghost.v @@ -0,0 +1,93 @@ +Require Import skylabs.auto.cpp.prelude.proof. +Require Import iris.algebra.lib.frac_auth. + +(** Each worker receives half of a permission to enter the critical section. + The first worker deposits its half and returns the exclusive authority; + the second gathers both halves and transfers ownership of the counter. + Only the counter's parity is tracked. Joining both workers recovers its + ownership because they cannot both return the exclusive authority. *) +Module double_incr_protocol. + Canonical Structure completionR : cmra := frac_authR unitUR. + + Class G `{Σ : cpp_logic} := { + #[local] completion_own :: HasOwn (iPropI _Σ) completionR; + #[local] completion_upd :: HasOwnUpd (iPropI _Σ) completionR; + #[local] completion_valid :: HasOwnValid (iPropI _Σ) completionR; + }. + #[global] Arguments G {_ _} Σ : assert. + + Definition gname := iprop.gname. + + Lemma trim_add_two_even (n : Z) : + (n mod 2 = 0)%Z -> (trim 32 (n + 2) mod 2 = 0)%Z. + Proof. + intros Hn. rewrite /trim Z.mod_mod_divide. + - rewrite Z.add_mod; last lia. rewrite Hn. reflexivity. + - exists (2 ^ 31)%Z. reflexivity. + Qed. + + Section proof. + Context `{Σ : cpp_logic, σ : genv, !G Σ}. + + Definition auth (γ : iprop.gname) : mpred := + own γ ((●F ()) : completionR). + Definition frac (γ : iprop.gname) (q : Qp) : mpred := + own γ ((◯F{q} ()) : completionR). + + Definition evenR (x : ptr) : mpred := + ∃ n : Z, x |-> uintR 1$m n ** [| (n mod 2 = 0)%Z |]. + + Definition protected (γ : gname) (x : ptr) : mpred := + (auth γ ** evenR x) ∨ + (frac γ (1 / 2) ** evenR x) ∨ frac γ 1. + + Definition done (γ : gname) (x : ptr) : mpred := + auth γ ∨ evenR x. + + Lemma alloc (x : ptr) : + evenR x |-- (|==> ∃ γ, + protected γ x ** frac γ (1 / 2) ** frac γ (1 / 2)). + Proof. + iIntros "Hx". + iMod (own_alloc (((●F ()) ⋅ (◯F ())) : completionR)) + as (γ) "[Ha Hf]"; first (apply frac_auth_valid; done). + have Hsplit : ((◯F ()) : completionR) ≡ + ((◯F{1 / 2} ()) ⋅ (◯F{1 / 2} ())). + { rewrite -frac_auth_frag_op Qp.half_half. done. } + iEval (rewrite Hsplit own_op) in "Hf". + iModIntro. iExists γ. rewrite /frac. iFrame "Hf". + rewrite /protected. iLeft. iFrame. + Qed. + + (** After the physical increments, the first worker deposits its half and + the second gathers both halves. The completed invariant keeps the full + fraction, preventing another worker from presenting an unused half. *) + Lemma prepare γ (x : ptr) : + protected γ x ** frac γ (1 / 2) |-- + evenR x ** (evenR x -* protected γ x ** done γ x). + Proof. + rewrite /protected /done /auth /frac. + iIntros "[[H | [H | H]] Hf]". + - iDestruct "H" as "[Ha Hx]". iFrame "Hx". iIntros "Hx". + iSplitL "Hf Hx". + + iRight. iLeft. iFrame. + + iLeft. done. + - iDestruct "H" as "[Hstored Hx]". iFrame "Hx". iIntros "Hx". + iCombine "Hstored Hf" as "Hfull". + iSplitL "Hfull". + + iRight. iRight. done. + + iRight. done. + - iDestruct (own_valid_2 with "H Hf") as %Hv. + move: Hv. rewrite -frac_auth_frag_op frac_auth_frag_valid. + move=> [Hfrac _]. exfalso. compute in Hfrac. done. + Qed. + + Lemma join γ (x : ptr) : done γ x ** done γ x |-- evenR x. + Proof. + rewrite /done. iIntros "[[H1 | H1] [H2 | H2]]"; try iAssumption. + rewrite /auth. + iDestruct (own_valid_2 with "H1 H2") as %Hv. + move: Hv. rewrite auth_auth_op_valid. done. + Qed. + End proof. +End double_incr_protocol. From e8d5c56ccabc7b74f31986e45fcf10b6b423874d Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 17 Sep 2026 15:20:03 -0400 Subject: [PATCH 035/109] Apply suggestion from @simon-skylabs --- rocq-brick-libstdcpp/proof/thread/spec.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index 47caefca..911ebcfa 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -71,7 +71,7 @@ Section with_cpp. \require child <> parent \post this |-> R None ** Q). - cpp.spec "std::thread::thread, void>(void (*&)())" as ctor_spec from source with + cpp.spec "std::thread::thread, void>(void ( *&)())" as ctor_spec from source with (\exact Reduce spawn_spec_body). cpp.spec "std::thread::thread, void>(void (&)())" From 54a00fdc837e9e62ca5175ed52a555b7e819052e Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 18 Sep 2026 06:28:45 +0200 Subject: [PATCH 036/109] Update dune.inc --- rocq-brick-libstdcpp/proof/dune.inc | 12 ++++++++++++ rocq-brick-libstdcpp/test/dune.inc | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/rocq-brick-libstdcpp/proof/dune.inc b/rocq-brick-libstdcpp/proof/dune.inc index b173654d..2dcf6668 100644 --- a/rocq-brick-libstdcpp/proof/dune.inc +++ b/rocq-brick-libstdcpp/proof/dune.inc @@ -227,6 +227,18 @@ (with-stderr-to inc_string_cpp.v.stderr (run cpp2v -v %{input} -o inc_string_cpp.v --no-elaborate --templates=inc_string_cpp_templates.v -- -std=c++20 -stdlib=libstdc++ )))) (alias (name srcs) (deps inc_string.cpp)) ) +(subdir thread + (rule + (targets thread_hpp.v.stderr thread_hpp.v thread_hpp_templates.v) + (alias test_ast) + (deps + (:input thread.hpp) + (env_var CPP2V_DOCKER_ENABLED) + (glob_files_rec ../*.hpp)) + (action + (with-stderr-to thread_hpp.v.stderr (run cpp2v -v %{input} -o thread_hpp.v --no-elaborate --templates=thread_hpp_templates.v -- -std=c++20 -stdlib=libstdc++ )))) + (alias (name srcs) (deps thread.hpp)) +) (subdir vector (rule (targets inc_vector_cpp.v.stderr inc_vector_cpp.v inc_vector_cpp_templates.v) diff --git a/rocq-brick-libstdcpp/test/dune.inc b/rocq-brick-libstdcpp/test/dune.inc index 43e3a819..de81fa8c 100644 --- a/rocq-brick-libstdcpp/test/dune.inc +++ b/rocq-brick-libstdcpp/test/dune.inc @@ -323,6 +323,18 @@ (with-stderr-to custom_recursive_mutex_hpp.v.stderr (run cpp2v -v %{input} -o custom_recursive_mutex_hpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) (alias (name srcs) (deps custom_recursive_mutex.hpp)) ) +(subdir mutex + (rule + (targets double_incr_cpp.v.stderr double_incr_cpp.v) + (alias test_ast) + (deps + (:input double_incr.cpp) + (env_var CPP2V_DOCKER_ENABLED) + (glob_files_rec ../*.hpp)) + (action + (with-stderr-to double_incr_cpp.v.stderr (run cpp2v -v %{input} -o double_incr_cpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) + (alias (name srcs) (deps double_incr.cpp)) +) (subdir mutex (rule (targets guard_recursive_cpp.v.stderr guard_recursive_cpp.v) From cf0aa926c87da986fe4ab63dbde019a05d3cc4d5 Mon Sep 17 00:00:00 2001 From: Ke Du <46699625+dkxb@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:54:01 -0500 Subject: [PATCH 037/109] [mutex.v] use Axiom for assumptions in Prop. Co-authored-by: Paolo G. Giarrusso --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 27860bbb..edbc5a6c 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -114,7 +114,7 @@ Module Type MUTEX_PREDS. Initializing [this |-> R old 1$m emp] probably depends on the mutex implementation. *) - Parameter init_R : forall (this : ptr) (old : gname) + Axiom init_R : forall (this : ptr) (old : gname) (pool : iprop.gname) (P : mpred), WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- From 9e6f4c3cadd632b93bbe922358953adbe8d61d98 Mon Sep 17 00:00:00 2001 From: Ke Du <46699625+dkxb@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:54:45 -0500 Subject: [PATCH 038/109] [mutex.v] use Axiom for assumptions in Prop. Co-authored-by: Paolo G. Giarrusso --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index edbc5a6c..1b7e894e 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -125,7 +125,7 @@ Module Type MUTEX_PREDS. (** Register a thread by consuming its handle for this mutex's namespace and its token share. [my_mutexes_alloc_mutex_name] splits this handle from the full namespace pool supplied when the thread is spawned. *) - Parameter register_thread : forall + Axiom register_thread : forall (this : ptr) (g : gname) (q : cQp.t) (P : mpred) (th : thread_idT) (qt : cQp.t), this |-> R g q P ** token g qt ** From 7656a02cb0da940aaa872d49cbb23ca590614966 Mon Sep 17 00:00:00 2001 From: Ke Du <46699625+dkxb@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:49:06 -0500 Subject: [PATCH 039/109] Update rocq-brick-libstdcpp/proof/thread/spec.v Co-authored-by: Paolo G. Giarrusso --- rocq-brick-libstdcpp/proof/thread/spec.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index 911ebcfa..24f9842d 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -18,7 +18,7 @@ Section with_cpp. #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(type_ptr="std::thread")] derive R. #[global] Declare Instance R_exclusive state (this : ptr) : - Exclusive0 (this |-> R state). + Exclusive0 (R state). (** The mutex-set map tracks spawned thread IDs and supplies each child with its [MutexSets.my_mutexes] handle. *) From 0e51e79f513a020ff8c83419219b8e1fc063a3aa Mon Sep 17 00:00:00 2001 From: Ke Du <46699625+dkxb@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:49:24 -0500 Subject: [PATCH 040/109] Update rocq-brick-libstdcpp/proof/lib/lock_ghost2.v Co-authored-by: Paolo G. Giarrusso --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 2bc8f9dc..29c60df9 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -167,7 +167,7 @@ End OWNER_TID. Module MutexSets : MUTEX_SETS. Canonical Structure cmraR : cmra := - discrete_funUR (fun _ : thread_idT => coPset_disjR). + thread_idT -d> coPset_disjR Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; From 9092cb2c8352e9e7da43a2dca77db005402ea178 Mon Sep 17 00:00:00 2001 From: Ke Du <46699625+dkxb@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:50:27 -0500 Subject: [PATCH 041/109] Update rocq-brick-libstdcpp/proof/lib/lock_ghost2.v Co-authored-by: Paolo G. Giarrusso --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 29c60df9..521dcd94 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -42,7 +42,7 @@ Module Type MUTEX_SETS. `{Σ : cpp_logic, !G Σ} γ th E : Objective (my_mutexes γ th E). Parameter my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), - E1 ∩ E2 <> ∅ -> + ~(E1 ## E2) -> my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2) |-- False. Parameter alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, From 5a63a48477d884915a2d46bc905f57752120d05c Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Fri, 18 Sep 2026 20:43:22 +0000 Subject: [PATCH 042/109] [lock_guard.v] remove qt from R; in the specs, bind qt to mutex.locked/unlocked --- .../proof/mutex/spec/lock_guard.v | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 287840f6..c1583c05 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -16,10 +16,10 @@ End TO_UPSTREAM. Module lock_guard. sl.lock - Definition R `{Σ : cpp_logic, !HasStdThreads Σ} {σ : genv} - (mp : ptr * mutex.gname * Qp * cQp.t) (q : cQp.t) (P : mpred) : Rep := + Definition R `{Σ : cpp_logic, !HasStdThreads Σ, !mutex.G Σ} {σ : genv} + (mp : ptr * mutex.gname * cQp.t) (q : cQp.t) (P : mpred) : Rep := structR "std::lock_guard" q ** - let '(mp, g, q', _) := mp in + let '(mp, g, q') := mp in _field "std::lock_guard::_M_device" |-> refR<"std::mutex"> q mp ** pureR ( mp |-> mutex.R g (q * q')$m P). @@ -31,7 +31,7 @@ Module lock_guard. Import rep.RepFor. Import RepScheme. - #[global] Instance repfor `{Σ : cpp_logic, !HasStdThreads Σ} {σ : genv} : + #[global] Instance repfor `{Σ : cpp_logic, !HasStdThreads Σ, !mutex.G Σ} {σ : genv} : rep.RepFor.C "std::lock_guard" [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] R := {}. @@ -88,32 +88,32 @@ Section with_cpp. \this this \arg{mp} "m" (Vptr mp) \persist{thr} current_thread thr - \pre{g q qt P} mp |-> mutex.R g q$m P - \pre mutex.not_locked mp g thr qt + \pre{g q P} mp |-> mutex.R g q P + \pre{qt} mutex.not_locked mp g thr qt \post - this |-> R (mp, g, q, qt) 1$m P ** + this |-> R (mp, g, q) 1$m P ** P ** mutex.locked mp g thr qt ). cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( \this this - \pre{mp g q qt P} this |-> R (mp, g, q, qt) 1$m P + \pre{mp g q P} this |-> R (mp, g, q) 1$m P \persist{thr} current_thread thr - \pre mutex.locked mp g thr qt + \pre{qt} mutex.locked mp g thr qt \pre ▷P \post mutex.not_locked mp g thr qt ** - mp |-> mutex.R g q$m P + mp |-> mutex.R g q P ). Section with_prelude. Import skylabs.auto.cpp.prelude.proof. - Lemma mutex_borrow mp g P (this : ptr) (q1 q2 : Qp) (qt : cQp.t) : - this |-> R (mp, g, (q1 + q2)%Qp, qt) 1$m P |-- + Lemma mutex_borrow mp g P (this : ptr) (q1 q2 : Qp) : + this |-> R (mp, g, ((q1 + q2)$m)%cQp) 1$m P |-- mp |-> mutex.R g q1$m P ** - this |-> R (mp, g, q2, qt) 1$m P. + this |-> R (mp, g, q2$m)%cQp 1$m P. Proof. rewrite R.unlock. work. From 6cf2f0e8babbe226e32f08e16be4a4c1e78a564e Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 22 Sep 2026 20:54:41 +0000 Subject: [PATCH 043/109] fix lock_ghost2.v --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 521dcd94..95424f65 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -167,7 +167,7 @@ End OWNER_TID. Module MutexSets : MUTEX_SETS. Canonical Structure cmraR : cmra := - thread_idT -d> coPset_disjR + thread_idT -d> coPset_disjR. Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; @@ -209,7 +209,7 @@ Module MutexSets : MUTEX_SETS. Context `{Σ : cpp_logic, !G Σ}. Lemma my_mutexes_exclusive γ th (E1 E2 : coPset) : - E1 ∩ E2 <> ∅ -> + ~(E1 ## E2) -> my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2) |-- False. Proof. From 78578b8e613a95a940f3e5c86a32861e86e040c0 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 24 Sep 2026 03:20:00 +0000 Subject: [PATCH 044/109] refactor [recursive_mutex.v] to only contain the spec part; the atomic specs and the proof that they derive the non-atomic specs are in [recursive_mutex_atomic.v] --- .../proof/mutex/demo_cpp_proof.v | 43 +- .../mutex/proof/unique_lock_recursive_mutex.v | 3 +- .../proof/mutex/spec/recursive_mutex.v | 937 ++++++------------ .../proof/mutex/spec/recursive_mutex_atomic.v | 542 ++++++++++ .../mutex/custom_recursive_mutex_hpp_proof.v | 34 +- .../test/mutex/guard_recursive_cpp_proof.v | 59 +- 6 files changed, 930 insertions(+), 688 deletions(-) create mode 100644 rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v diff --git a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v index c7d36864..c1d5e25d 100644 --- a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v +++ b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v @@ -22,7 +22,7 @@ Definition TT : tele := [tele (_ : Z) (_ : Z)]. (** Canonical "constructor" for our telescope. *) Polymorphic Definition mk (a b : Z) : TT := {| tele_arg_head := a; tele_arg_tail := {| tele_arg_head := b; tele_arg_tail := () |} |}. -Succeed Definition b := recursive_mutex.Held 0 (mk 0 0). +Succeed Definition b := std_recursive_mutex.Held 0 (mk 0 0). (** Proof that [mk] is injective. Doesn't appear to be necessary here. *) #[global] Instance mk_inj: Inj2 eq eq eq mk. @@ -34,32 +34,30 @@ Definition P `{Σ : cpp_logic, σ : genv} (this : ptr) : TT -t> mpred := sl.lock Definition CR - `{Σ : cpp_logic, σ : genv, HasOwn (iPropI _) recursive_mutex.cmraR, !recursive_mutex.lockedG Σ} - (γ : recursive_mutex.rmutex_gname) (q : cQp.t) : Rep := + `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (γ : std_recursive_mutex.gname) (q : cQp.t) : Rep := structR "C" q ** - _field "C::mut" |-> recursive_mutex.derivedR γ q ** as_Rep (fun this : ptr => - recursive_mutex.inv_rmutex γ (∃ a_b : tele_arg TT, - @tele_app TT (fun _ => mpred) (P this) a_b)). + this ,, _field "C::mut" |-> std_recursive_mutex.R γ q + (∃ a_b : tele_arg _, @tele_app TT (fun _ => mpred) (P this) a_b)). #[only(cfractional,ascfractional,type_ptr)] derive CR. #[only(lazy_unfold)] derive CR. -Section recursive_mutex. - Import recursive_mutex. +Section std_recursive_mutex. + Import std_recursive_mutex. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context {has_rmutex : HasOwn (iPropI _) recursive_mutex.cmraR}. - Context `{!recursive_mutex.lockedG Σ}. + Context `{!std_recursive_mutex.G Σ}. Lemma acquireable_update_equiv {TT : tele} γ th f t1 t2 P : acquire t1 t2 -> acquireable γ th (update f t1) P ⊣⊢ acquireable γ th (release (TT := TT) (update f t2)) P. Proof. intros. - by erewrite recursive_mutex.update_eq. + by erewrite std_recursive_mutex.update_eq. Qed. -End recursive_mutex. +End std_recursive_mutex. #[only(fwd(l2r))] derive acquireable_update_equiv. #[only(bwd(l2r))] derive acquireable_update_equiv. @@ -67,8 +65,7 @@ End recursive_mutex. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context {has_rmutex : HasOwn (iPropI _) recursive_mutex.cmraR}. - Context `{!recursive_mutex.lockedG Σ}. + Context `{!std_recursive_mutex.G Σ}. #[global] Instance: LearnEq2 CR'. Proof. solve_learnable. Qed. @@ -77,15 +74,15 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post recursive_mutex.acquireable γ th (TT:=TT) (recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). + \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). cpp.spec "C::update_b(long)" as C_update_b from demo_cpp.source with (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post recursive_mutex.acquireable γ th (TT:=TT) (recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). + \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). #[global] Instance CR_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) CR). Proof. solve_learnable. Qed. @@ -145,18 +142,22 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post recursive_mutex.acquireable γ th (TT:=TT) (recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). + \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). Lemma transfer_ok : verify[source] "C::transfer(int)". Proof. verify_spec; go. destruct args as [a [b []]]; work. + pose (updated := mk (trim 64 (a + x)) (trim 64 (b + (0 - x)))). + iExists (std_recursive_mutex.acquireable γ th + (std_recursive_mutex.release (std_recursive_mutex.Held n updated)) (P this)), updated, n. + go. Qed. Lemma partial_transfer_link : denoteModule source ∗ - recursive_mutex.lock_spec_alt' ∗ recursive_mutex.unlock_spec_alt' + std_recursive_mutex.std_lock_spec_alt ∗ std_recursive_mutex.std_unlock_spec_alt ⊢ C_transfer_int. Proof. work. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v index dbb6e62e..c6e52929 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v @@ -12,8 +12,7 @@ NES.Begin unique_lock. Context `{Σ : cpp_logic} {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. - Context `{!recursive_mutex.lockedG Σ}. - Context `{!HasOwn (iPropI _) recursive_mutex.cmraR}. + Context `{!std_recursive_mutex.G Σ}. Import R_unfold. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 666a7a05..23bfc9a7 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -3,8 +3,6 @@ Require Import iris.algebra.lib.excl_auth. Require Import skylabs.bi.tls_modalities. Require Import skylabs.bi.tls_modalities_rep. -Require Import skylabs.bi.weakly_objective. -Require Import skylabs.auto.cpp.weakly_local_with. Require Import skylabs.auto.cpp.proof. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. @@ -16,297 +14,22 @@ Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Import linearity. -Module recursive_mutex. - - (* Not prodO thread_idTO natO. *) - (* A thread that has zero, locked γ th 0 does not even know which thread has non-0. *) - Canonical Structure phys_stateUR := authR (optionR (exclR (prodO thread_idTO natO))). - - (** <> <> owns the mutex <<γ>> <> times. *) - Class lockedG `{Σ : cpp_logic} := { - #[local] has_lock_ghost :: lock_ghost.lockG Σ; - - #[local] has_phys_state :: HasOwn (iPropI _Σ) phys_stateUR; - #[local] has_phys_state_upd :: HasOwnUpd (iPropI _Σ) phys_stateUR; - #[local] has_phys_state_valid :: HasOwnValid (iPropI _Σ) phys_stateUR; - #[local] has_phys_state_unit :: HasOwnUnit mpredI phys_stateUR; - }. - #[global] Arguments lockedG {_ _} Σ : assert. - - Record gname : Set := MkGname - { owned_count_id : iprop.gname; - locked_gname : iprop.gname; - inv_gname : iprop.gname; - }. - - (** [owned_count_id_auth γ Some (th, n)] implies that the lock's count is [n + 1]. *) - sl.lock - Definition owned_count_id_auth `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (om : option (thread_idT * natO)) : mpred := - own γ.(owned_count_id) (● (option_map Excl om)). - #[only(timeless)] derive owned_count_id_auth. - - (** [owned_count_id_frag γ Some (th, n)] implies that the lock's count is [n + 1]. *) - sl.lock - Definition owned_count_id_frag `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (om : option (thread_idT * natO)) : mpred := - own γ.(owned_count_id) (◯ (option_map Excl om)). - #[only(timeless)] derive owned_count_id_frag. - - #[local] Open Scope nat_scope. - - (** [locked γ th n] implies that the lock's count is [n]: see [used_threads]'s - definition and [owned_count_id_auth]'s informal contract. *) - sl.lock - Definition locked `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (th : thread_idT) (n : nat) : mpred := - user γ.(locked_gname) th ** - match n with - | 0 => owned_count_id_frag γ None - | S n => owned_count_id_frag γ (Some (th, n)) - end. - #[only(timeless)] derive locked. - - (* TODO: we should abstract this over the ownership that is produced and - then it can be used more generally. *) - sl.lock - Definition used_threads - `{Σ : cpp_logic, !lockedG Σ, !HasStdThreads Σ} - (γ : gname) (s : gset thread_idT) : mpred := - lock_ghost.used_threads γ.(locked_gname) s. - #[only(timeless)] derive used_threads. - - Section locked_with_cpp. - Context `{Σ : cpp_logic}. - Context `{!lockedG Σ}. - Context `{!HasStdThreads Σ}. - - Lemma use_thread th g s : - th ∉ s -> - used_threads g s |-- - (|==> used_threads g (s ∪ {[ th ]}) ** locked g th 0). - Proof. - rewrite used_threads.unlock locked.unlock owned_count_id_frag.unlock /=. - iIntros (Hni) "A". - iMod (lock_ghost.login with "A") as "[$ $]"; first done. - iApply own_unit. - Qed. - - Lemma logout th g s : - th ∉ s -> - used_threads g (s ∪ {[ th ]}) ** locked g th 0 |-- - (|==> used_threads g s ** owned_count_id_frag g None). - Proof. - rewrite used_threads.unlock locked.unlock. - iIntros (Hni) "(? & ? & $)". - iApply lock_ghost.logout; first done. - work. - Qed. - - #[global] Instance owned_count_id_frag_WeaklyObjective γ om : - WeaklyObjective (PROP := iPropI _) (owned_count_id_frag γ om). - Proof. rewrite owned_count_id_frag.unlock. apply _. Qed. - - #[global] Instance - locked_WeaklyObjective γ thr n : - WeaklyObjective (PROP := iPropI _) (locked γ thr n). - Proof. rewrite locked.unlock. apply _. Qed. - - Lemma locked_excl_same_thread g th n m : - locked g th n ** locked g th m |-- False. - Proof. - rewrite locked.unlock. - work. - iDestruct (user_unique with "[$]") as "[]". - Qed. - - Lemma locked_excl_different_thread g th th' n m : - locked g th n ** locked g th' m |-- [| n = 0 \/ m = 0 |] ** True. - Proof. - destruct (decide (th = th')) as [->|Hne]. { - rewrite locked_excl_same_thread. work. - } - rewrite locked.unlock. - iIntros "[[_ A] [_ B]]". - destruct n, m; try auto. iExFalso. - rewrite owned_count_id_frag.unlock. - iDestruct (own_valid_2 with "A B") as %HV; exfalso. - rewrite -auth_frag_op auth_frag_valid in HV. - done. - Qed. - - End locked_with_cpp. - -(** -Underlying pthread implementation for [PTHREAD_MUTEX_RECURSIVE_NP] case: - - << - /* Check whether we already hold the mutex. */ - if (mutex->__data.__owner == id) - { - /* Just bump the counter. */ - if (__glibc_unlikely (mutex->__data.__count + 1 == 0)) - /* Overflow of the counter. */ - return EAGAIN; - - ++mutex->__data.__count; - - return 0; - } - (* LLL_MUTEX_LOCK_OPTIMIZED (mutex); *) - LLL_MUTEX_LOCK (mutex); - >> - -Informally, we can read mutex->__data.__owner atomically, and we know that -mutex->__data.__owner == id if and only if our thread has completed locking the -recursive mutex; hence, mutex->__data.__owner != id means that nobody is -touching the mutex or other threads are operating on it, but at no point will they set __owner to our ID. - -Hence: -1. [if (mutex->__data.__owner == id)], we can get obtain sequential ownership of -mutex->__data.__count, and of the underlying resources, and complete the lock operation. -2. else, we can attempt to grab the underlying non-recursive lock, and be sure we - won't deadlock against ourselves. - -Formalizing step 1 seems nontrivial, but relatively routine. -But the full pthread implementation would add annoying details. - -The right invariant might resemble the following, but significant details are TBD. -[[ -cinv ( - \exists x, - mutex->__data.__owner |-> x ** - if bool_decide (x <> 0) then - (sequential ownership of count ** ownership of data protected by the lock) \/ - some exclusive token (* needed to take the sequential out *) - else - emp - ) -]] -*) - (* the mask of recursive_mutex *) - Definition mask := nroot .@@ "std" .@@ "recursive_mutex" .@@ "mask". - - (** We base the implementation protocol on - https://github.com/bminor/glibc/blob/04e750e75b73957cf1c791535a3f4319534a52fc/nptl/pthread_mutex_lock.c#L90-L112. - - official mirror: - https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_mutex_lock.c;h=a697f2b6ca8dfa9e4557ab3f44b87bc5ceeec014;hb=HEAD#l90 - TODO: revise. - *) - - (* NOTE: Invariant used to protect resource [r] - - [[ - inv (r \\// exists th n, locked th (S n)) - ]] - *) - - - (** Intended meaning: ownership of physical C++ state for an instance of "std::recursive_mutex". *) - Parameter rawR : ∀ `{Σ : cpp_logic, σ : genv}, option thread_idT -> nat -> Rep. - (* The thread_idT is None (0) if there is no owner. *) - #[only(type_ptr="std::recursive_mutex")] derive rawR. - - Definition rmutex_N : namespace := - nroot .@@ "std" .@@ "recursive_mutex" .@ "raw_inv". - - (* recursive mutex -- ownership of the class. *) - sl.lock - Definition I `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) : Rep := - type_ptrR "std::recursive_mutex" ** - cinv rmutex_N γ.(inv_gname) (∃ owner count, rawR owner count ** - (* We use [Nat.pred] because [owned_count_id_auth] stores [counter - 1]. *) - pureR (owned_count_id_auth γ ((λ t, (t, Nat.pred count)) <$> owner))). - (* TODO: readd [|owner = None <-> count = O|] elsewhere, as sequential invariant in [R]. *) - #[only(knowledge,type_ptr="std::recursive_mutex")] derive I. - - sl.lock - Definition R `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := - type_ptrR "std::recursive_mutex" ** - (* TODO: add here sequential ownership of the lock, and maybe replace I by the lock invariant. - Something like *) - (* _mutex_field |-> mutex.R q ... ** *) - pureR (cinv_own γ.(inv_gname) q). - #[only(cfracsplittable,type_ptr="std::recursive_mutex")] derive R. - - - Section base_construction. - Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. - Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!lockedG Σ}. - - #[global] Instance I_learn : Cbn (Learn (learn_eq ==> learn_hints.fin) I). - Proof. solve_learnable. Qed. - #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) R). - Proof. solve_learnable. Qed. - - cpp.spec "std::recursive_mutex::recursive_mutex()" as ctor_spec with - (\this this - \post Exists g, this |-> R g 1$m ** used_threads g empty). - - cpp.spec "std::recursive_mutex::~recursive_mutex()" as dtor_spec with - (\this this - \pre{g} this |-> R g 1$m - \pre used_threads g empty - \post emp). - - cpp.spec "std::recursive_mutex::lock()" as lock_spec with - (\this this - \prepost{q g} this |-> R g q (* part of both pre and post *) - \persist{th} current_thread th - \pre{Q} AC << ∀ n , locked g th n >> @ top \ ↑ mask , empty - << locked g th (S n) , COMM Q >> - \post Q). - - cpp.spec "std::recursive_mutex::unlock()" as unlock_spec with - (\this this - \prepost{q g} this |-> R g q (* part of both pre and post *) - \persist{th} current_thread th - \pre{Q} AC << ∀ n , locked g th (S n) >> @ top \ ↑ mask , empty - << locked g th n , COMM Q >> - \post Q). - - End base_construction. - - - (** * Derived construction *) - Record rmutex_gname := - { lock_gname : gname - ; level_gname : iprop.gname - ; cinv_gname : iprop.gname - }. - Definition rmutex_namespace := nroot .@@ "std" .@@ "recursive_mutex" .@@ "derived". - - Canonical Structure cmraR := (excl_authR (prodO natO thread_idTO)). - - sl.lock - Definition inv_rmutex - `{Σ : cpp_logic} `{!lockedG Σ} `{!HasOwn (iPropI _) cmraR} - (g : rmutex_gname) (P : mpred) : mpred := - cinv rmutex_namespace g.(cinv_gname) - (Exists n th, own g.(level_gname) (●E (n, th)) ** - match n with - | 0 => P ** own g.(level_gname) (◯E (n, th)) - | S n => locked g.(lock_gname) th (S n) - end). - #[only(knowledge)] derive inv_rmutex. - - (** Fractional ownership of the physical recursive mutex and of the - cancellable invariant that protects the custom resource P. - The two fractions do not have to be the same, we just choose to make them - equal for convenience. - *) - sl.lock - Definition derivedR - `{Σ : cpp_logic, σ : genv, !lockedG Σ} - (g : rmutex_gname) (q : cQp.t) : Rep := - R g.(lock_gname) q ** - pureR (cinv_own g.(cinv_gname) q). - #[only(cfracsplittable,type_ptr="std::recursive_mutex")] derive derivedR. - - (** [acquire_state] tracks the acquisition state of a recursive_mutex. - *) +(** * This file contains: + - The logical states ([RecursiveMutexState]) + - An interface of predicates [RECURSIVE_MUTEX_PREDS] + - Recursive mutex specs [recursive_mutex_spec] + - And specs that are bound to stdlib names [StdRecursiveMutex] *) + +(** The C++ class a recursive-mutex specification is stated at. *) +Module Type MutexCPPName. + Parameter cpp_ty : type. +End MutexCPPName. + +(** Logical state of the recursive mutex. + A recursive mutex starts in NotHeld, lock increment the held count, + and unlock decrements it: + NotHeld <-> Held 0 _ <-> Held 1 _ <-> ... *) +Module RecursiveMutexState. Inductive acquire_state {TT : tele} : Type := | NotHeld (* not held *) | Held (n : nat) (xs : TT) (* acquired [n + 1] times with quantifiers [xs] *). @@ -341,352 +64,348 @@ cinv ( end end. - sl.lock - Definition acquireable - `{Σ : cpp_logic, !lockedG Σ, !HasStdThreads Σ, !HasOwn (iPropI _) cmraR} - (g : rmutex_gname) (th : thread_idT) {TT: tele} (t : acquire_state TT) - (P : TT -t> mpred) : mpred := - current_thread th ** - match t with - | NotHeld => locked g.(lock_gname) th 0 - | Held n args => own g.(level_gname) (◯E (S n, th)) ** - @tele_app TT (fun _ => mpred) P args + Lemma is_held {TT : tele} {t1 t2 : acquire_state TT} : + acquire t1 t2 -> + ∃ n xs, t2 = Held n xs /\ t1 = release t2. + Proof. + rewrite acquire.unlock release.unlock. + intros. destruct t1; simpl in H; eauto. + - exists 0. naive_solver. + - exists (S n). naive_solver. + Qed. + + Definition update {TT : tele} (f : TT -t> TT) + (x : acquire_state TT) : acquire_state TT := + match x with + | NotHeld => NotHeld + | Held n xs => Held n (tele_app f xs) end. + Lemma update_eq {TT : tele} f t1 t2 : acquire t1 t2 -> + update f t1 = release (TT := TT) (update f t2). + Proof. + by intros ([|] & ? & -> & ->)%is_held; rewrite !release.unlock. + Qed. +End RecursiveMutexState. + +(** Predicates of a recursive_mutex. *) +Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). + Include RecursiveMutexState. + Definition cpp_ty : type := T.cpp_ty. + Parameter gname : Set. + Parameter G : forall `{Σ : cpp_logic}, Type. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + + Parameter used_threads : forall `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ}, gname -> gset thread_idT -> mpred. + Parameter held_token : forall `{Σ : cpp_logic, !G Σ}, + gname -> thread_idT -> nat -> mpred. + (* [acquireable γ th s P] contains an acquire state [s] and, if the acquire + state is held, the resource P. *) + Parameter acquireable : forall `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ}, gname -> thread_idT -> + forall {TT : tele}, acquire_state TT -> (TT -t> mpred) -> mpred. + (* abstract predicate of owership of a recursive mutex that protects some + resource [P], e.g. in the form of [inv _ P]. Note that it only represents + the mutex data structure, not the ownership of [P]. *) + Parameter R : forall `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv}, + gname -> cQp.t -> mpred -> Rep. + #[global] Hint Opaque used_threads held_token acquireable R : sl_opacity typeclass_instances. + Section with_cpp. - Context `{Σ : cpp_logic}. + Context `{Σ : cpp_logic, !G Σ}. + Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!HasOwn (iPropI _) cmraR, !HasStdThreads Σ}. - Context `{!lockedG Σ}. + #[global] Declare Instance held_token_timeless g th n : + Timeless (held_token g th n). + #[global] Declare Instance acquireable_current_thread : + `{Observe (current_thread th) (acquireable g th (TT := TT) t P)}. - #[global] Instance acquireable_learn γ th TT : LearnEq2 (acquireable γ th (TT := TT)). - Proof. solve_learnable. Qed. + Axiom acquireable_Held : forall g th {TT : tele} n (xs : TT) P, + acquireable g th (Held n xs) P ⊣⊢ + current_thread th ** held_token g th n ** @tele_app TT (fun _ => mpred) P xs. - #[global] Instance acquireable_current_thread : - `{Observe (current_thread th) (acquireable g th (TT := TT) t P)}. - Proof. rewrite acquireable.unlock; apply _. Qed. + Axiom use_thread_acquirable : forall {TT : tele} th g m P, + th ∉ m -> + current_thread th ** used_threads g m |-- (|==> + used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P). - Lemma use_thread_acquirable {TT} th g m P : + Axiom logout_acquirable : forall {TT : tele} th g m P, th ∉ m -> - current_thread th ** used_threads g.(lock_gname) m |-- (|==> - used_threads g.(lock_gname) (m ∪ {[ th ]}) ** acquireable (TT := TT) g th NotHeld P). - Proof. - rewrite acquireable.unlock /=. - work. - wapply use_thread; first done. - work with br_erefl. - iModIntro; work. - Qed. + used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P |-- + (|==> used_threads g m). + + Context {σ : genv}. + #[only(cfractional,cfracvalid,ascfractional)] derive R. + #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). End with_cpp. +End RECURSIVE_MUTEX_PREDS. +(** Non-atomic specifications and their automation use only the public rules. *) +Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T). + Import Preds. Section with_cpp. - Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. + Context `{Σ : cpp_logic, !G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!lockedG Σ}. - Context `{!HasOwn (iPropI _) cmraR}. - - (* Alternative style: - << - R γ q r ** locked γ th (S n) |--| R γ q r ** r ** was_locked γ th (S n) - >> - - possible solution: two specs/choice in the spec for unlock: either - <<{locked γ th (n+1)} unlock() {locked γ th n}>> - or - <<{was_locked γ th (n+2)} unlock() {locked γ th (n+1)}>> - *) - - (* TODO make this into a hint *) - Lemma is_held {TT : tele} {t1 t2 : acquire_state TT} : - acquire t1 t2 -> - ∃ n xs, t2 = Held n xs /\ - t1 = release t2. - Proof. - rewrite acquire.unlock release.unlock. - intros. - destruct t1; simpl in H; eauto. - - exists 0. naive_solver. - - exists (S n). naive_solver. - Qed. - - #[program] - Definition acquireable_is_acquired_C {TT} g th t t' P - (_ : acquire (TT := TT) t t') := - \cancelx - \consuming acquireable g th t' P - \deduce{args} @tele_app TT (fun _ => mpred) P args - \deduce{n} [| t' = Held n args /\ t = release t' |] - \deduce own g.(level_gname) (◯E (S n, th)) - \end. - Next Obligation. - intros * (? & ? & -> & ->)%is_held. - rewrite acquireable.unlock. - ego. - Qed. - - #[program] - Definition acquireable_acquireable_C γ := - \cancelx - \consuming{th n TT args P} acquireable (TT := TT) γ th (Held n args) P - \bound P' - \bound_existential th' args' - \proving acquireable γ th' args' P' - \instantiate th' := th - \instantiate args' := Held n args - \deduce @tele_app TT (fun _ => mpred) P args - \through @tele_app TT (fun _ => mpred) P' args - \end. - Next Obligation. rewrite acquireable.unlock; work. Qed. - - #[program] - Definition own_P_is_acquireable_C {TT} g n P := - \cancelx - \preserving{th} current_thread th - \consuming own g.(level_gname) (◯E (S n, th)) - \bound n' args - \proving acquireable (TT := TT) g th (Held n' args) P - \through @tele_app TT (fun _ => mpred) P args - \through [| n' = n |] - \end. - Next Obligation. rewrite acquireable.unlock; work. Qed. - - #[global] Instance : `{Learnable - (current_thread th) - (acquireable (TT := TT0) γ th0 args P0) - [th0 = th] }. - Proof. solve_learnable. Qed. - - #[global] Instance learn_inv_rmutex_γ : `{Learnable - (inv_rmutex γ1 P1) - (inv_rmutex γ2 P2) - [γ2 = γ1] }. - Proof. solve_learnable. Qed. - - #[global] Instance learn_inv_rmutex_TT : `{Learnable - (inv_rmutex γ (∃ xs : tele_arg TT1, - @tele_app TT1 (fun _ => mpred) P1 xs)) - (inv_rmutex γ (∃ xs : tele_arg TT2, - @tele_app TT2 (fun _ => mpred) P2 xs)) - [TT2 = TT1] }. - Proof. solve_learnable. Qed. - - #[global] Instance learn_inv_rmutex_P TT : `{Learnable - (inv_rmutex γ1 (∃ xs : tele_arg TT, - @tele_app TT (fun _ => mpred) P1 xs)) - (inv_rmutex γ2 (∃ xs : tele_arg TT, - @tele_app TT (fun _ => mpred) P2 xs)) - [P2 = P1] }. - Proof. solve_learnable. Qed. - - #[global] Instance learn_args - {TT: tele} (t : acquire_state TT) (P : TT -t> mpred) : - `{Learnable - (@tele_app TT (fun _ => mpred) P args ** - own (level_gname γ) (◯E (S n, th))) - (acquireable γ th t P) - [t = Held n args] }. - Proof. solve_learnable. Qed. - - Definition update {TT : tele} (f : TT -t> TT) - (x : acquire_state TT) : acquire_state TT := - match x with - | NotHeld => NotHeld - | Held n xs => Held n (@tele_app TT (fun _ => TT) f xs) - end. - - Lemma update_eq {TT : tele} f t1 t2 : acquire t1 t2 -> - update f t1 = release (TT := TT) (update f t2). - Proof. - by intros ([|] & ? & -> & ->)%is_held; rewrite !release.unlock. - Qed. + Context {σ : genv}. - (* this is the usable pre-condition *) - cpp.spec "std::recursive_mutex::recursive_mutex()" as ctor_spec' with + Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \persist{th} current_thread th - \pre{TT P xs} |> @tele_app TT (fun _ => mpred) P xs - \require ∀ xs, WeaklyObjective (@tele_app TT (fun _ => mpred) P xs) - \post - Exists g, - this |-> derivedR g 1$m ** - used_threads g.(lock_gname) empty ** - inv_rmutex g (∃ xs, @tele_app TT (fun _ => mpred) P xs)). - - cpp.spec "std::recursive_mutex::~recursive_mutex()" as dtor_spec' with + \persist{th} current_thread th + \pre{TT P xs} |> @tele_app TT (fun _ => mpred) P xs + \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) + \post Exists g, + this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** used_threads g empty). + + Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{g} this |-> derivedR g 1 - \pre used_threads g.(lock_gname) empty - \pre{TT P} inv_rmutex g (∃ xs, - @tele_app TT (fun _ => mpred) P xs) - \post |> (Exists xs, @tele_app TT (fun _ => mpred) P xs)). + \pre{g TT P} this |-> R g 1 (∃ xs, @tele_app TT (fun _ => mpred) P xs) + \pre used_threads g empty + \post |> (Exists xs, @tele_app TT (fun _ => mpred) P xs)). - cpp.spec "std::recursive_mutex::lock()" as lock_spec' with + Definition lock_spec : ptr -> WpSpec mpred val val := (\this this - \persist{g TT P} inv_rmutex g (∃ xs, - @tele_app TT (fun _ => mpred) P xs) - \prepost{q} this |-> derivedR g q - \pre{th n} acquireable g th n P - \post (Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P)). - (* to prove: this is derivable from lock_spec *) - - cpp.spec "std::recursive_mutex::unlock()" as unlock_spec' with + \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) + \pre{th n} acquireable g th n P + \post Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P). + + Definition unlock_spec : ptr -> WpSpec mpred val val := (\this this - \persist{g TT P} inv_rmutex g (∃ xs, - @tele_app TT (fun _ => mpred) P xs) - \prepost{q} this |-> derivedR g q - \pre{th n args} acquireable g th (Held n args) P - \post acquireable g th (release $ Held n args) P). - - Definition do_lock g K : mpred := ∃ TT P th n, - inv_rmutex g (∃ xs, @tele_app TT (fun _ => mpred) P xs) - ** acquireable g th n P - ** ( - (* TODO readd *) - (* ▷ *) - (Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P) -* - K). + \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) + \pre{th n args} acquireable g th (Held n args) P + \post acquireable g th (release $ Held n args) P). + + Definition do_lock (lk : gname * mpred) (K : mpred) : mpred := + ∃ TT P th n, + [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** + acquireable lk.1 th n P ** + ((* TODO readd *) + (* ▷ *) + (Exists n', [| acquire n n' |] ** ▷ acquireable lk.1 th n' P) -* K). #[global] Arguments do_lock /. - Definition do_unlock g K : mpred := ∃ TT P th n args, - inv_rmutex g (∃ xs, @tele_app TT (fun _ => mpred) P xs) - ** acquireable g th (Held n args) P - ** ( - (* TODO readd *) + Definition do_unlock (lk : gname * mpred) (K : mpred) : mpred := + ∃ TT P th n args, + [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** + acquireable lk.1 th (Held n args) P ** + ((* TODO readd *) (* ▷ *) - acquireable g th (release $ Held n args) P -* - K). + acquireable lk.1 th (release $ Held n args) P -* K). #[global] Arguments do_unlock /. #[global] Instance recursive_mutex_basic_lockable : BasicLockable - (T:=rmutex_gname) "std::recursive_mutex" (λ q g, derivedR g q) := - { do_lock := fun this => do_lock - ; do_unlock := fun this => do_unlock }. - - cpp.spec "std::recursive_mutex::lock()" as lock_spec_alt' with - (\exact Reduce (lock_basic_lockable "std::recursive_mutex" (λ q g, derivedR g q))). - - cpp.spec "std::recursive_mutex::unlock()" as unlock_spec_alt' with - (\exact Reduce (unlock_basic_lockable "std::recursive_mutex" (λ q g, derivedR g q))). - - Lemma lock_spec'_equiv_lock_spec_alt' : - lock_spec' -|- lock_spec_alt'. - Proof. iSplit; iApply specify_mono; ework with br_erefl. Qed. - - Lemma unlock_spec'_equiv_unlock_spec_alt' : - unlock_spec' -|- unlock_spec_alt'. - Proof. iSplit; iApply specify_mono; ework with br_erefl. Qed. - - Definition acquireable_current_thread_F := - ltac:(mk_obs_fwd acquireable_current_thread). - #[local] Hint Resolve acquireable_current_thread_F : br_hints. - - (* TODO AUTO *) - #[global] Instance later_acquireable_learn γ th TT : - LearnEq2 (fun a b => bi_later (acquireable γ th (TT := TT) a b)). - Proof. solve_learnable. Qed. - - Import linearity. - - Context `{HOV : !HasOwnValid mpredI cmraR, HOU : !HasOwnUpd mpredI cmraR}. - - Lemma ctor_spec_impl_ctor_spec' : - ctor_spec |-- ctor_spec'. - Proof using MOD HOV HOU. - apply specify_mono_fupd; work. - iModIntro; work. - rewrite /acquireable /=. - iMod (own_alloc (●E (O, th) ⋅ ◯E (O, th))) as (g) "(? & ?)". - { apply excl_auth_valid. } - wname [used_threads] "u". - wname [_ |-> _ _ _] "a". - iMod (cinv_alloc with "[-u a]") as (ginv) "(#Hinv & Hown)"; last first. - - iExists {| lock_gname := t; level_gname := g; cinv_gname:= ginv |}. - rewrite derivedR.unlock inv_rmutex.unlock /=. - iModIntro. - go with br_erefl $usenamed=true. - - ework with br_erefl. - - apply _. - Qed. + (T := gname * mpred) cpp_ty (fun q gP => R gP.1 q gP.2) := + { do_lock := fun this => do_lock + ; do_unlock := fun this => do_unlock }. - Lemma dtor_spec_impl_dtor_spec' : - dtor_spec |-- dtor_spec'. - Proof using MOD HOV HOU. - apply specify_mono_fupd; work. - rewrite derivedR.unlock inv_rmutex.unlock. - work. - iMod (cinv_cancel with "[$] [$]") as (n th) "(>? & ?)"; [done..|]. - - destruct n as [|n'] eqn:?; work; first last. - { - rewrite locked.unlock used_threads.unlock. - wapply used_threads_empty_no_not_locked; work with br_erefl. - } - iModIntro. work. iModIntro. ego with br_erefl. - (* _now_ we just need to leak ghost state and that's okay *) - wname [cinv] "I"; wname [own] "L1"; wname [own] "L2"; iCombine "L1 L2" as "L". - iApply (affine with "L"). apply mpred_BiAffine. - Qed. + Definition lock_spec_alt : ptr -> WpSpec mpred val val := + lock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). + Definition unlock_spec_alt : ptr -> WpSpec mpred val val := + unlock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). - Lemma lock_spec_impl_lock_spec' : - lock_spec |-- lock_spec'. - Proof using MOD HOV HOU. - apply specify_mono; rewrite derivedR.unlock; work. - iExists q, (cinv_own g.(cinv_gname) q ** - (∃ t, [| acquire n t |] ∗ ▷ acquireable g th t P))%I. - wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock acquireable.unlock. - work; iAcIntro; rewrite /commit_acc/=; work. - iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". - destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]; work. - 2: iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. - all: work $usenamed=true; iApply fupd_mask_intro; first set_solver; - iIntros "Hclose'"; work; iMod "Hclose'" as "_". - - destruct n'; first last. { - iMod "Hcases". - iDestruct (locked_excl_different_thread with "[$]") as (?) "?". - exfalso. lia. - } - rewrite bi.later_sep bi.later_exist_except_0. - iDestruct "Hcases" as "(>(%args & ?) & >Hcase)". - iMod (own_update_2 with "Hn Hcase") as "(Hg & ?)"; - first apply (excl_auth_update _ _ (1, th)). - wname [recursive_mutex.locked _ th _] "Hlocked"; - iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). work. - - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; - first apply (excl_auth_update _ _ (S (S n), th)). - wname [recursive_mutex.locked _ th _] "Hlocked"; - iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). work. + Lemma lock_spec_equiv_lock_spec_alt this xs K : + lock_spec this xs K ⊣⊢ lock_spec_alt this xs K. + Proof. + unfold lock_spec, lock_spec_alt, lock_basic_lockable, do_lock. + cbn. iSplit. + - iIntros "H". iDestruct "H" as (g TT P q th n) + "(%Hxs & HR & HA & HK)". + iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), + (Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P)%I. + iFrame "HR HK". iSplit; first done. + iExists TT, P, th, n. iFrame "HA". + iSplit; first done. iIntros "$". + - iIntros "H". iDestruct "H" as (q [g J] K') + "(%Hxs & HR & Hdo & HK)". + iDestruct "Hdo" as (TT P th n) "(%Heq & HA & Hcont)". + simpl in *. subst J. + iExists g, TT, P, q, th, n. iFrame "HR HA". + iSplit; first done. iIntros "[HR Hac]". + iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. - Lemma unlock_spec_impl_unlock_spec' : - unlock_spec |-- unlock_spec'. - Proof using MOD HOV HOU. - apply specify_mono; rewrite derivedR.unlock; work. - iExists q, (cinv_own g.(cinv_gname) q ** - acquireable g th (release $ Held n args) P)%I. - wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock acquireable.unlock. - work; iAcIntro; rewrite /commit_acc/=; work. - iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". - iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. - iMod "Hcases". - iApply fupd_mask_intro; first set_solver; iIntros "Hclose'". - iExists n; work $usenamed=true. - iMod "Hclose'" as "_". - iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; - first apply (excl_auth_update _ _ (n, th)). - rewrite release.unlock; destruct n; iFrame "#∗". - all: iMod ("Hclose" with "[-]") as "_"; - ework $usenamed=true with br_erefl; done. + Lemma unlock_spec_equiv_unlock_spec_alt this xs K : + unlock_spec this xs K ⊣⊢ unlock_spec_alt this xs K. + Proof. + unfold unlock_spec, unlock_spec_alt, unlock_basic_lockable, do_unlock. + cbn. iSplit. + - iIntros "H". iDestruct "H" as (g TT P q th n args) + "(%Hxs & HR & HA & HK)". + iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), + (acquireable g th (release $ Held n args) P). + iFrame "HR HK". iSplit; first done. + iExists TT, P, th, n, args. iFrame "HA". + iSplit; first done. iIntros "$". + - iIntros "H". iDestruct "H" as (q [g J] K') + "(%Hxs & HR & Hdo & HK)". + iDestruct "Hdo" as (TT P th n args) "(%Heq & HA & Hcont)". + simpl in *. subst J. + iExists g, TT, P, q, th, n, args. iFrame "HR HA". + iSplit; first done. iIntros "[HR Hac]". + iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. + Section proof_automation. + #[global] Instance R_learn : + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + (** Recover the resource telescope before matching its arguments. *) + #[global] Instance R_learn_TT : `{Learnable + (R g q1 (∃ xs : tele_arg TT1, @tele_app TT1 (fun _ => mpred) P1 xs)) + (R g q2 (∃ xs : tele_arg TT2, @tele_app TT2 (fun _ => mpred) P2 xs)) + [TT2 = TT1] }. + Proof. solve_learnable. Qed. + + #[global] Instance R_learn_P TT : `{Learnable + (R g q1 (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P1 xs)) + (R g q2 (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P2 xs)) + [P2 = P1] }. + Proof. solve_learnable. Qed. + + (** The mutex representation remembers the resource after clients open it. *) + #[global] Instance R_acquireable_learn_TT (this : ptr) : `{Learnable + (this |-> R g q (∃ xs : tele_arg TT1, @tele_app TT1 (fun _ => mpred) P1 xs)) + (acquireable g th (TT := TT2) t P2) + [TT2 = TT1] }. + Proof. solve_learnable. Qed. + + #[global] Instance R_acquireable_learn_P TT (this : ptr) : `{Learnable + (this |-> R g q (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P1 xs)) + (acquireable g th (TT := TT) t P2) + [P2 = P1] }. + Proof. solve_learnable. Qed. + + #[global] Instance acquireable_learn_TT : `{Learnable + (acquireable g th (TT := TT1) t1 P1) + (acquireable g th (TT := TT2) t2 P2) + [TT2 = TT1] }. + Proof. solve_learnable. Qed. + + #[global] Instance acquireable_learn γ th TT : + LearnEq2 (acquireable γ th (TT := TT)). + Proof. solve_learnable. Qed. + #[global] Instance held_token_learn γ th : LearnEq1 (held_token γ th). + Proof. solve_learnable. Qed. + #[global] Instance later_acquireable_learn γ th TT : + LearnEq2 (fun a b => bi_later (acquireable γ th (TT := TT) a b)). + Proof. solve_learnable. Qed. + + #[global] Instance : `{Learnable + (current_thread th) + (acquireable (TT := TT0) γ th0 args P0) + [th0 = th] }. + Proof. solve_learnable. Qed. + + #[global] Instance learn_args + {TT : tele} (t : acquire_state TT) (P : TT -t> mpred) : + `{Learnable + (@tele_app TT (fun _ => mpred) P args ** held_token γ th n) + (acquireable γ th t P) + [t = Held n args] }. + Proof. solve_learnable. Qed. + + Definition acquireable_current_thread_F := + ltac:(mk_obs_fwd acquireable_current_thread). + + #[program] + Definition acquireable_is_acquired_C {TT} g th t t' P + (_ : acquire (TT := TT) t t') := + \cancelx + \consuming acquireable g th t' P + \deduce{args} @tele_app TT (fun _ => mpred) P args + \deduce{n} [| t' = Held n args /\ t = release t' |] + \deduce held_token g th n + \end. + Next Obligation. + intros * (? & ? & -> & ->)%is_held. + rewrite acquireable_Held. ego. + Qed. + + #[program] + Definition acquireable_acquireable_C γ := + \cancelx + \consuming{th n TT args P} acquireable (TT := TT) γ th (Held n args) P + \bound P' + \bound_existential th' args' + \proving acquireable γ th' args' P' + \instantiate th' := th + \instantiate args' := Held n args + \deduce @tele_app TT (fun _ => mpred) P args + \through @tele_app TT (fun _ => mpred) P' args + \end. + Next Obligation. intros. rewrite acquireable_Held; work; rewrite acquireable_Held; work. Qed. + + #[program] + Definition own_P_is_acquireable_C {TT} g n P := + \cancelx + \preserving{th} current_thread th + \consuming held_token g th n + \bound n' args + \proving acquireable (TT := TT) g th (Held n' args) P + \through @tele_app TT (fun _ => mpred) P args + \through [| n' = n |] + \end. + Next Obligation. + intros. iIntros "[#Hth Htoken]" (n' args) "[HP %Heq]". + subst n'. rewrite acquireable_Held. iFrame "#∗". + Qed. + End proof_automation. End with_cpp. - #[global] Hint Resolve acquireable_acquireable_C : br_hints. - #[global] Hint Resolve acquireable_is_acquired_C : br_hints. - #[global] Hint Resolve own_P_is_acquireable_C : br_hints. - #[global] Hint Resolve acquireable_current_thread_F : br_hints. + #[global] Hint Resolve acquireable_acquireable_C acquireable_is_acquired_C + own_P_is_acquireable_C acquireable_current_thread_F : br_hints. +End recursive_mutex_spec. + +Module StdRecursiveMutexName. + Definition cpp_ty : type := "std::recursive_mutex"%cpp_type. + #[global] Hint Opaque cpp_ty : sl_opacity. + Definition cpp_N : bs := "std::recursive_mutex". +End StdRecursiveMutexName. + + +(** Bind the reusable specifications to std::recursive_mutex names, but with + a parametrized RECURSIVE_MUTEX_PREDS. *) +Module StdRecursiveMutex + (Preds : RECURSIVE_MUTEX_PREDS StdRecursiveMutexName). + Include Preds. + #[global] Hint Opaque cpp_ty : sl_opacity. + Module Spec := recursive_mutex_spec StdRecursiveMutexName Preds. + Include Spec. + + Section with_cpp. + Context `{Σ : cpp_logic, !G Σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MOD : source ⊧ σ}. + + cpp.spec "std::recursive_mutex::recursive_mutex()" as std_ctor_spec with + (\exact Reduce Spec.ctor_spec). + cpp.spec "std::recursive_mutex::~recursive_mutex()" as std_dtor_spec with + (\exact Reduce Spec.dtor_spec). + cpp.spec "std::recursive_mutex::lock()" as std_lock_spec with + (\exact Reduce Spec.lock_spec). + cpp.spec "std::recursive_mutex::unlock()" as std_unlock_spec with + (\exact Reduce Spec.unlock_spec). + cpp.spec "std::recursive_mutex::lock()" as std_lock_spec_alt with + (\exact Reduce Spec.lock_spec_alt). + cpp.spec "std::recursive_mutex::unlock()" as std_unlock_spec_alt with + (\exact Reduce Spec.unlock_spec_alt). + + Lemma std_lock_spec_equiv_std_lock_spec_alt : std_lock_spec -|- std_lock_spec_alt. + Proof. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.lock_spec_equiv_lock_spec_alt; done. + Qed. + Lemma std_unlock_spec_equiv_std_unlock_spec_alt : std_unlock_spec -|- std_unlock_spec_alt. + Proof. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.unlock_spec_equiv_unlock_spec_alt; done. + Qed. + End with_cpp. +End StdRecursiveMutex. -End recursive_mutex. +(* export a recursive_mutex module bound to StdRecursiveMutexName but + RECURSIVE_MUTEX_PREDS is still abstract.*) +Declare Module StdRecursiveMutexPreds : RECURSIVE_MUTEX_PREDS StdRecursiveMutexName. +Module std_recursive_mutex := StdRecursiveMutex StdRecursiveMutexPreds. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v new file mode 100644 index 00000000..4c7eb7da --- /dev/null +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -0,0 +1,542 @@ +Require Import iris.algebra.gset. +Require Import iris.algebra.lib.excl_auth. + +Require Import skylabs.bi.tls_modalities. +Require Import skylabs.bi.tls_modalities_rep. +Require Import skylabs.bi.weakly_objective. +Require Import skylabs.auto.cpp.weakly_local_with. + +Require Import skylabs.auto.cpp.proof. +Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Export skylabs.brick.libstdcpp.runtime.pred. + +Require Import skylabs.brick.libstdcpp.mutex.requirements. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. + +Require Export skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. + +Import linearity. + +(** This file contains: + - Atomic specs for recursive mutex [RecursiveMutexAtomicSpecs]. + - An instantiation of (non-atomic) recursive mutex predicates + (which can be plugged into [recursive_mutex_spec]). + - A proof that the non-atomic specs refine the atomic version. +*) + +(** Atomic specs for recursive mutex. *) +Module RecursiveMutexAtomicSpecs (T : MutexCPPName). + (* FIXME the hardcoded strings can be derived from T.cpp_ty. *) + + (* Not prodO thread_idTO natO. *) + (* at_specs thread that has zero, locked γ th 0 does not even know which thread has non-0. *) + Canonical Structure phys_stateUR := authR (optionR (exclR (prodO thread_idTO natO))). + + (** <> <> owns the mutex <<γ>> <> times. *) + Class lockedG `{Σ : cpp_logic} := { + #[local] has_lock_ghost :: lock_ghost.lockG Σ; + + #[local] has_phys_state :: HasOwn (iPropI _Σ) phys_stateUR; + #[local] has_phys_state_upd :: HasOwnUpd (iPropI _Σ) phys_stateUR; + #[local] has_phys_state_valid :: HasOwnValid (iPropI _Σ) phys_stateUR; + #[local] has_phys_state_unit :: HasOwnUnit mpredI phys_stateUR; + }. + #[global] Arguments lockedG {_ _} Σ : assert. + + Record gname : Set := MkGname + { owned_count_id : iprop.gname; + locked_gname : iprop.gname; + inv_gname : iprop.gname; + }. + + (** [owned_count_id_auth γ Some (th, n)] implies that the lock's count is [n + 1]. *) + sl.lock + Definition owned_count_id_auth `{Σ : cpp_logic, !lockedG Σ} + (γ : gname) (om : option (thread_idT * natO)) : mpred := + own γ.(owned_count_id) (● (option_map Excl om)). + #[only(timeless)] derive owned_count_id_auth. + + (** [owned_count_id_frag γ Some (th, n)] implies that the lock's count is [n + 1]. *) + sl.lock + Definition owned_count_id_frag `{Σ : cpp_logic, !lockedG Σ} + (γ : gname) (om : option (thread_idT * natO)) : mpred := + own γ.(owned_count_id) (◯ (option_map Excl om)). + #[only(timeless)] derive owned_count_id_frag. + + #[local] Open Scope nat_scope. + + (** [locked γ th n] implies that the lock's count is [n]: see [used_threads]'s + definition and [owned_count_id_auth]'s informal contract. *) + sl.lock + Definition locked `{Σ : cpp_logic, !lockedG Σ} + (γ : gname) (th : thread_idT) (n : nat) : mpred := + user γ.(locked_gname) th ** + match n with + | 0 => owned_count_id_frag γ None + | S n => owned_count_id_frag γ (Some (th, n)) + end. + #[only(timeless)] derive locked. + + (* TODO: we should abstract this over the ownership that is produced and + then it can be used more generally. *) + sl.lock + Definition used_threads + `{Σ : cpp_logic, !lockedG Σ, !HasStdThreads Σ} + (γ : gname) (s : gset thread_idT) : mpred := + lock_ghost.used_threads γ.(locked_gname) s. + #[only(timeless)] derive used_threads. + + Section locked_with_cpp. + Context `{Σ : cpp_logic}. + Context `{!lockedG Σ}. + Context `{!HasStdThreads Σ}. + + Lemma use_thread th g s : + th ∉ s -> + used_threads g s |-- + (|==> used_threads g (s ∪ {[ th ]}) ** locked g th 0). + Proof. + rewrite used_threads.unlock locked.unlock owned_count_id_frag.unlock /=. + iIntros (Hni) "at_specs". + iMod (lock_ghost.login with "at_specs") as "[$ $]"; first done. + iApply own_unit. + Qed. + + Lemma logout th g s : + th ∉ s -> + used_threads g (s ∪ {[ th ]}) ** locked g th 0 |-- + (|==> used_threads g s ** owned_count_id_frag g None). + Proof. + rewrite used_threads.unlock locked.unlock. + iIntros (Hni) "(? & ? & $)". + iApply lock_ghost.logout; first done. + work. + Qed. + + #[global] Instance owned_count_id_frag_WeaklyObjective γ om : + WeaklyObjective (PROP := iPropI _) (owned_count_id_frag γ om). + Proof. rewrite owned_count_id_frag.unlock. apply _. Qed. + + #[global] Instance + locked_WeaklyObjective γ thr n : + WeaklyObjective (PROP := iPropI _) (locked γ thr n). + Proof. rewrite locked.unlock. apply _. Qed. + + Lemma locked_excl_same_thread g th n m : + locked g th n ** locked g th m |-- False. + Proof. + rewrite locked.unlock. + work. + iDestruct (user_unique with "[$]") as "[]". + Qed. + + Lemma locked_excl_different_thread g th th' n m : + locked g th n ** locked g th' m |-- [| n = 0 \/ m = 0 |] ** True. + Proof. + destruct (decide (th = th')) as [->|Hne]. { + rewrite locked_excl_same_thread. work. + } + rewrite locked.unlock. + iIntros "[[_ at_specs] [_ B]]". + destruct n, m; try auto. iExFalso. + rewrite owned_count_id_frag.unlock. + iDestruct (own_valid_2 with "at_specs B") as %HV; exfalso. + rewrite -auth_frag_op auth_frag_valid in HV. + done. + Qed. + + End locked_with_cpp. + +(** +Underlying pthread implementation for [PTHREAD_MUTEX_RECURSIVE_NP] case: + + << + /* Check whether we already hold the mutex. */ + if (mutex->__data.__owner == id) + { + /* Just bump the counter. */ + if (__glibc_unlikely (mutex->__data.__count + 1 == 0)) + /* Overflow of the counter. */ + return EAGAIN; + + ++mutex->__data.__count; + + return 0; + } + (* LLL_MUTEX_LOCK_OPTIMIZED (mutex); *) + LLL_MUTEX_LOCK (mutex); + >> + +Informally, we can read mutex->__data.__owner atomically, and we know that +mutex->__data.__owner == id if and only if our thread has completed locking the +recursive mutex; hence, mutex->__data.__owner != id means that nobody is +touching the mutex or other threads are operating on it, but at no point will they set __owner to our ID. + +Hence: +1. [if (mutex->__data.__owner == id)], we can get sequential ownership of +mutex->__data.__count, and of the underlying resources, and complete the lock operation. +2. else, we can attempt to grab the underlying non-recursive lock, and be sure we + won't deadlock against ourselves. + +Formalizing step 1 seems nontrivial, but relatively routine. +But the full pthread implementation would add annoying details. + +The right invariant might resemble the following, but significant details are TBD. +[[ +cinv ( + \exists x, + mutex->__data.__owner |-> x ** + if bool_decide (x <> 0) then + (sequential ownership of count ** ownership of data protected by the lock) \/ + some exclusive token (* needed to take the sequential out *) + else + emp + ) +]] +*) + (* the mask of recursive_mutex *) + Definition mask := nroot .@@ "std" .@@ "recursive_mutex" .@@ "mask". + + (** We base the implementation protocol on + https://github.com/bminor/glibc/blob/04e750e75b73957cf1c791535a3f4319534a52fc/nptl/pthread_mutex_lock.c#L90-L112. + + official mirror: + https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_mutex_lock.c;h=a697f2b6ca8dfa9e4557ab3f44b87bc5ceeec014;hb=HEAD#l90 + TODO: revise. + *) + + (* NOTE: Invariant used to protect resource [r] + + [[ + inv (r \\// exists th n, locked th (S n)) + ]] + *) + + + (** Intended meaning: ownership of physical C++ state for an instance of "std::recursive_mutex". *) + Parameter rawR : ∀ `{Σ : cpp_logic, σ : genv}, option thread_idT -> nat -> Rep. + (* The thread_idT is None (0) if there is no owner. *) + Axiom rawR_type_ptr : ∀ `{Σ : cpp_logic, σ : genv} o n, + Observe (type_ptrR T.cpp_ty) (rawR o n). + #[global] Existing Instance rawR_type_ptr. + + Definition rmutex_N : namespace := + nroot .@@ "std" .@@ "recursive_mutex" .@ "raw_inv". + + (* recursive mutex -- ownership of the class. *) + sl.lock + Definition I `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) : Rep := + type_ptrR T.cpp_ty ** + cinv rmutex_N γ.(inv_gname) (∃ owner count, rawR owner count ** + (* We use [Nat.pred] because [owned_count_id_auth] stores [counter - 1]. *) + pureR (owned_count_id_auth γ ((λ t, (t, Nat.pred count)) <$> owner))). + (* TODO: readd [|owner = None <-> count = O|] elsewhere, as sequential invariant in [R]. *) + #[only(knowledge (*,type_ptr="std::recursive_mutex"*) )] derive I. + + sl.lock + Definition R `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := + type_ptrR T.cpp_ty ** + (* TODO: add here sequential ownership of the lock, and maybe replace I by the lock invariant. + Something like *) + (* _mutex_field |-> mutex.R q ... ** *) + pureR (cinv_own γ.(inv_gname) q). + #[only(cfracsplittable)] derive R. + #[global] Instance R_type_ptr `{Σ : cpp_logic, σ : genv, !lockedG Σ} γ q : + Observe (type_ptrR T.cpp_ty) (R γ q). + Proof. rewrite R.unlock. apply _. Qed. + + + Section base_construction. + Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!lockedG Σ}. + + #[global] Instance I_learn : Cbn (Learn (learn_eq ==> learn_hints.fin) I). + Proof. solve_learnable. Qed. + #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + + Definition ctor_spec_body : ptr -> WpSpec mpred val val := + (\this this + \post Exists g, this |-> R g 1$m ** used_threads g empty). + + Definition dtor_spec_body : ptr -> WpSpec mpred val val := + (\this this + \pre{g} this |-> R g 1$m + \pre used_threads g empty + \post emp). + + Definition lock_spec_body : ptr -> WpSpec mpred val val := + (\this this + \prepost{q g} this |-> R g q (* part of both pre and post *) + \persist{th} current_thread th + \pre{Q} AC << ∀ n , locked g th n >> @ top \ ↑ mask , empty + << locked g th (S n) , COMM Q >> + \post Q). + + Definition unlock_spec_body : ptr -> WpSpec mpred val val := + (\this this + \prepost{q g} this |-> R g q (* part of both pre and post *) + \persist{th} current_thread th + \pre{Q} AC << ∀ n , locked g th (S n) >> @ top \ ↑ mask , empty + << locked g th n , COMM Q >> + \post Q). + + End base_construction. +End RecursiveMutexAtomicSpecs. + +(** An instantiation of RecursiveMutexPreds. This is built on top of predicates + in RecursiveMutexAtomicSpecs, so the abstraction here is not ideal. + The main result here is that the atomic specs can derive the non-atomic + ones; see the [RecursiveMutexRefinement] module defined later. *) +Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. + Module at_specs := RecursiveMutexAtomicSpecs T. + Include RecursiveMutexState. + + Definition cpp_ty : type := T.cpp_ty. + #[global] Hint Opaque cpp_ty : sl_opacity. + + Record rmutex_gname := MkGname + { lock_gname : at_specs.gname + ; level_gname : iprop.gname + ; cinv_gname : iprop.gname + }. + Definition gname := rmutex_gname. + + Canonical Structure cmraR := excl_authR (prodO natO thread_idTO). + + Class recursive_mutexG `{Σ : cpp_logic} := { + #[global] locked_G :: at_specs.lockedG Σ; + #[global] level_own :: HasOwn (iPropI _Σ) cmraR; + }. + Definition G := @recursive_mutexG. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + #[global] Instance recursive_mutex_G `{Σ : cpp_logic} (H : G Σ) : + @recursive_mutexG _ _ Σ := H. + + Definition rmutex_namespace := + nroot .@@ "std" .@@ "recursive_mutex" .@@ "derived". + + sl.lock + Definition inv_rmutex `{Σ : cpp_logic, !G Σ} + (g : gname) (P : mpred) : mpred := + cinv rmutex_namespace g.(cinv_gname) + (Exists n th, own g.(level_gname) (●E (n, th)) ** + match n with + | 0 => P ** own g.(level_gname) (◯E (n, th)) + | S n => at_specs.locked g.(lock_gname) th (S n) + end). + #[only(knowledge)] derive inv_rmutex. + + (** Fractional ownership of the physical recursive mutex and of the + cancellable invariant that protects the custom resource P. + The two fractions do not have to be the same, we just choose to make them + equal for convenience. + *) + sl.lock + Definition derivedR `{Σ : cpp_logic, σ : genv, !G Σ} + (g : gname) (q : cQp.t) : Rep := + at_specs.R g.(lock_gname) q ** + pureR (cinv_own g.(cinv_gname) q). + #[only(cfracsplittable)] derive derivedR. + #[global] Instance derivedR_type_ptr `{Σ : cpp_logic, σ : genv, !G Σ} g q : + Observe (type_ptrR cpp_ty) (derivedR g q). + Proof. rewrite derivedR.unlock /cpp_ty. apply _. Qed. + + (** Public ownership packages the physical handle with its resource protocol. *) + Definition R `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} + {σ : genv} (g : gname) (q : cQp.t) (P : mpred) : Rep := + derivedR g q ** pureR (inv_rmutex g P). + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(cfractional,cfracvalid,ascfractional)] derive R. + #[global] Instance R_type_ptr `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv} g q P : + Observe (type_ptrR cpp_ty) (R g q P). + Proof. rewrite /R. apply _. Qed. + + Definition used_threads `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} + (g : gname) (threads : gset thread_idT) : mpred := + at_specs.used_threads g.(lock_gname) threads. + #[global] Hint Opaque used_threads : sl_opacity typeclass_instances. + #[only(timeless)] derive used_threads. + + Definition held_token `{Σ : cpp_logic, !G Σ} + (g : gname) (th : thread_idT) (n : nat) : mpred := + own g.(level_gname) (◯E (S n, th)). + #[global] Hint Opaque held_token : sl_opacity typeclass_instances. + #[only(timeless)] derive held_token. + + Definition acquireable `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} + (g : gname) (th : thread_idT) {TT : tele} (t : acquire_state TT) + (P : TT -t> mpred) : mpred := + current_thread th ** + match t with + | NotHeld => at_specs.locked g.(lock_gname) th 0 + | Held n args => held_token g th n ** tele_app P args + end. + + #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. + + Section rules. + Context `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ}. + + Lemma acquireable_Held g th {TT : tele} n (args : TT) P : + acquireable g th (Held n args) P ⊣⊢ + current_thread th ** held_token g th n ** tele_app P args. + Proof. rewrite /acquireable /=. done. Qed. + + #[global] Instance acquireable_current_thread : + `{Observe (current_thread th) (acquireable g th (TT := TT) t P)}. + Proof. rewrite /acquireable; apply _. Qed. + + Lemma use_thread_acquirable {TT} th g m P : + th ∉ m -> + current_thread th ** used_threads g m |-- (|==> + used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P). + Proof. + rewrite /used_threads /acquireable /=. + work. wapply at_specs.use_thread; first done. + work with br_erefl. iModIntro; work. + Qed. + + Lemma logout_acquirable {TT} th g m P : + th ∉ m -> + used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P |-- + (|==> used_threads g m). + Proof. + rewrite /used_threads /acquireable /=. + iIntros (Hnot) "[HU [_ HL]]". + iMod (at_specs.logout th g.(lock_gname) m Hnot + with "[$HU $HL]") as "(HU & Hunit)". + iModIntro. + iFrame "HU". iApply (affine with "Hunit"). apply mpred_BiAffine. + Qed. + End rules. +End RecursiveMutexPreds. + +(** Atomic specs derive the non-atomic specs. *) +Module RecursiveMutexRefinement (T : MutexCPPName). + Module Preds := RecursiveMutexPreds T. + Module Spec := recursive_mutex_spec T Preds. + Import Preds. + +Section with_cpp. + Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!G Σ}. + Context `{HOV : !HasOwnValid mpredI cmraR, HOU : !HasOwnUpd mpredI cmraR}. + + (* basically std_ctor_atomic_spec |-- std_ctor_non_atomic_spec *) + Lemma at_spec_impl_na_spec_ctor this : + elaborate.spec_entails_fupd + (Preds.at_specs.ctor_spec_body this) + (Spec.ctor_spec this). + Proof using MOD HOV HOU. + work. + iModIntro; work. + rewrite /acquireable /=. + iMod (own_alloc (●E (O, th) ⋅ ◯E (O, th))) as (g) "(? & ?)". + { apply excl_auth_valid. } + wname [Preds.at_specs.used_threads] "u". + wname [_ |-> _ _ _] "a". + iMod (cinv_alloc with "[-u a]") as (ginv) "(#Hinv & Hown)"; last first. + - iExists {| lock_gname := t; level_gname := g; cinv_gname:= ginv |}. + rewrite /R /used_threads _at_sep _at_pureR derivedR.unlock inv_rmutex.unlock /=. + iModIntro. + go with br_erefl $usenamed=true. + - ework with br_erefl. + - apply _. + Qed. + + Lemma at_spec_impl_na_spec_dtor this : + elaborate.spec_entails_fupd + (Preds.at_specs.dtor_spec_body this) + (Spec.dtor_spec this). + Proof using MOD HOV HOU. + work. + rewrite /R /used_threads _at_sep _at_pureR derivedR.unlock inv_rmutex.unlock. + work. + iMod (cinv_cancel with "[$] [$]") as (n th) "(>? & ?)"; [done..|]. + + destruct n as [|n'] eqn:?; work; first last. + { + rewrite Preds.at_specs.locked.unlock /used_threads Preds.at_specs.used_threads.unlock. + wapply lock_ghost.used_threads_empty_no_not_locked; work with br_erefl. + } + iModIntro. work. iModIntro. ego with br_erefl. + (* _now_ we just need to leak ghost state and that's okay *) + wname [own] "L1"; wname [own] "L2"; iCombine "L1 L2" as "L". + iApply (affine with "L"). apply mpred_BiAffine. + Qed. + + Lemma at_spec_impl_na_spec_lock this (xs : list val) (K : val -> mpred) : + Spec.lock_spec this xs K + |-- Preds.at_specs.lock_spec_body this xs K. + Proof using MOD HOV HOU. + revert xs K. + change (elaborate.spec_entails + (Preds.at_specs.lock_spec_body this) + (Spec.lock_spec this)). + work. + rewrite /R _at_sep _at_pureR derivedR.unlock; work. + iExists q, (cinv_own g.(cinv_gname) q ** + (∃ t, [| acquire n t |] ∗ ▷ acquireable g th t P))%I. + wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } + rewrite inv_rmutex.unlock /acquireable /held_token. + work; iAcIntro; rewrite /commit_acc/=; work. + iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". + destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]; work. + 2: iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. + all: work $usenamed=true; iApply fupd_mask_intro; first set_solver; + iIntros "Hclose'"; work; iMod "Hclose'" as "_". + - destruct n'; first last. { + iMod "Hcases". + iDestruct (Preds.at_specs.locked_excl_different_thread with "[$]") as (?) "?". + exfalso. lia. + } + rewrite bi.later_sep bi.later_exist_except_0. + iDestruct "Hcases" as "(>(%args & ?) & >Hcase)". + iMod (own_update_2 with "Hn Hcase") as "(Hg & ?)"; + first apply (excl_auth_update _ _ (1, th)). + wname [Preds.at_specs.locked _ th _] "Hlocked"; + iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. + iExists (Held 0 args). work. + - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; + first apply (excl_auth_update _ _ (S (S n), th)). + wname [Preds.at_specs.locked _ th _] "Hlocked"; + iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. + iExists (Held (S n) args). work. + Qed. + + Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : + Spec.unlock_spec this xs K + |-- Preds.at_specs.unlock_spec_body this xs K. + Proof using MOD HOV HOU. + revert xs K. + change (elaborate.spec_entails + (Preds.at_specs.unlock_spec_body this) + (Spec.unlock_spec this)). + work. + rewrite /R _at_sep _at_pureR derivedR.unlock; work. + iExists q, (cinv_own g.(cinv_gname) q ** + acquireable g th (release $ Held n args) P)%I. + wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } + rewrite inv_rmutex.unlock /acquireable /held_token. + work; iAcIntro; rewrite /commit_acc/=; work. + iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". + iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. + iMod "Hcases". + iApply fupd_mask_intro; first set_solver; iIntros "Hclose'". + iExists n; work $usenamed=true. + iMod "Hclose'" as "_". + iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; + first apply (excl_auth_update _ _ (n, th)). + rewrite release.unlock; destruct n; iFrame "#∗". + all: iMod ("Hclose" with "[-]") as "_"; + ework $usenamed=true with br_erefl; done. + Qed. + +End with_cpp. +End RecursiveMutexRefinement. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index b0929fd4..34ec407b 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -2,22 +2,11 @@ Require Import skylabs.auto.cpp.proof. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. +Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex_atomic. Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. -(* -TODO step 1: - -split recursive_mutex specs: - -* C++- independent predicates vs -* what's bound to C++ names - -* step2 -specs for atomic - -* step3??? - *) +(** FIXME don't need StdAtomicSpecs *) +Module StdAtomicSpecs := RecursiveMutexAtomicSpecs StdRecursiveMutexName. Module custom_recursive_mutex. Abbreviation N := "MyRecursiveMutex"%cpp_name. @@ -32,10 +21,10 @@ Module custom_recursive_mutex. Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. Record gname : Set := MkGname - { lock_gname : iprop.gname + { lock_gname : mutex.gname ; cinv_gname : iprop.gname ; excl_gname : iprop.gname - ; rec_gname : recursive_mutex.gname + ; rec_gname : StdAtomicSpecs.gname }. Definition lock_namespace : namespace := nroot .@@ "MyRecursiveMutex". @@ -43,15 +32,16 @@ Module custom_recursive_mutex. Parameter count_auth : ∀ `{Σ : cpp_logic} (count : nat), mpred. Parameter count_frag : ∀ `{Σ : cpp_logic} (count : nat), mpred. - (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := - ∃ x, recursive_mutex.owned_count_id_auth γ.(rec_gname) x. *) + (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !StdAtomicSpecs.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := + ∃ x, StdAtomicSpecs.owned_count_id_auth γ.(rec_gname) x. *) (* Definition rawR `{Σ : cpp_logic, σ : genv} (owner : option thread_idT) (count : nat) : Rep := structR "std::recursive_mutex" 1$m ** _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !recursive_mutex.lockedG Σ}. + Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !mutex.G Σ, + !StdAtomicSpecs.lockedG Σ}. Definition mutex_content (γ : gname) : Rep := ∃ count, @@ -70,15 +60,15 @@ Module custom_recursive_mutex. (this |-> mutex_content γ) ** (* *) (this |-> mutex_content γ - (* ** recursive_mutex.token γ.(rec_gname) (* ? *) *) + (* ** std_recursive_mutex.token γ.(rec_gname) (* ? *) *) \\// exclusive_token γ.(excl_gname) - (* ** recursive_mutex.given_token γ.(rec_gname) (* ? *) *) + (* ** std_recursive_mutex.given_token γ.(rec_gname) (* ? *) *) ) ** cinv lock_namespace γ.(cinv_gname) (∃ count owner, count_frag count ** this ,, _field "MyRecursiveMutex::m_owner" |-> atomic_thread_idT 1$m owner ** - recursive_mutex.owned_count_id_auth γ.(rec_gname) ((λ t, (t, Nat.pred count)) <$> owner) ** + StdAtomicSpecs.owned_count_id_auth γ.(rec_gname) ((λ t, (t, Nat.pred count)) <$> owner) ** [| owner = None <-> count = 0 |] ** match count with | 0 => emp diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index 456f5b52..2e197edc 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -68,7 +68,7 @@ Succeed #[global] Instance: Inhabited TT := _. (** Canonical "constructor" for our telescope. *) Polymorphic Definition mk (a : Z) : TT := {| tele_arg_head := a; tele_arg_tail := () |}. -Succeed Definition b := recursive_mutex.Held 0 (mk 0). +Succeed Definition b := std_recursive_mutex.Held 0 (mk 0). sl.lock Definition CR' `{Σ : cpp_logic, σ : genv} (a : Z) : Rep := @@ -83,14 +83,12 @@ Definition P `{Σ : cpp_logic, σ : genv} (this : ptr) : TT -t> mpred := sl.lock Definition CR `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ} - `{!recursive_mutex.lockedG Σ} - `{!HasOwn (iPropI _) recursive_mutex.cmraR} - (γ : recursive_mutex.rmutex_gname) (q : cQp.t) := + `{!std_recursive_mutex.G Σ} + (γ : std_recursive_mutex.gname) (q : cQp.t) := structR "C" q ** - _field "C::m" |-> recursive_mutex.derivedR γ q ** as_Rep (fun this : ptr => - recursive_mutex.inv_rmutex γ (∃ a : tele_arg TT, - @tele_app TT (fun _ => mpred) (P this) a)). + this ,, _field "C::m" |-> std_recursive_mutex.R γ q + (∃ a : tele_arg TT, @tele_app TT (fun _ => mpred) (P this) a)). #[only(cfractional,ascfractional,cfracvalid,type_ptr)] derive CR. #[only(lazy_unfold(export))] derive CR. @@ -98,11 +96,9 @@ Definition CR Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. - Context `{!recursive_mutex.lockedG Σ}. - Context `{!HasOwn (iPropI _) recursive_mutex.cmraR}. + Context `{!std_recursive_mutex.G Σ}. - #[local] Instance: - `{WeaklyObjective1 (@tele_app TT (fun _ => mpred) (P p))}. + #[local] Instance: `{Objective1 (@tele_app TT (fun _ => mpred) (P p))}. Proof. Admitted. #[global] Instance P_timeless p a : Timeless (P p a). @@ -120,15 +116,13 @@ Section with_cpp. \post[Vint 42] emp ). - Import recursive_mutex(lock_gname). - cpp.spec "C::C()" from source as C_ctor_spec with ( \this this \persist{thr} current_thread thr \post Exists γ, this |-> CR γ 1$m ** - recursive_mutex.used_threads (lock_gname γ) {[thr]} ** - recursive_mutex.acquireable (TT := TT) γ thr recursive_mutex.NotHeld (P this) + std_recursive_mutex.used_threads γ {[thr]} ** + std_recursive_mutex.acquireable (TT := TT) γ thr std_recursive_mutex.NotHeld (P this) ). (** XXX Does not appear to help *) @@ -143,9 +137,9 @@ Section with_cpp. rewrite {1}P.unlock. go. - iMod (recursive_mutex.use_thread thr (lock_gname t) ∅ with "[$]") as "?"; first set_solver; iModIntro. + iMod (std_recursive_mutex.use_thread_acquirable (TT := TT) thr t ∅ (P this) with "[$]") as "?"; first set_solver; iModIntro. go. - rewrite recursive_mutex.acquireable.unlock (left_id_L _ (∪)). + rewrite (left_id_L _ (∪)). go. Qed. @@ -153,26 +147,23 @@ Section with_cpp. \this this \persist{thr} current_thread thr \pre{γ} this |-> CR γ 1$m - (* \pre recursive_mutex.used_threads (lock_gname γ) empty *) - \pre recursive_mutex.used_threads γ.(lock_gname) {[thr]} - \pre recursive_mutex.inv_rmutex γ (∃ xs, - @tele_app TT (fun _ => mpred) (P this) xs) - \pre recursive_mutex.acquireable (TT := TT) γ thr recursive_mutex.NotHeld (P this) + (* \pre std_recursive_mutex.used_threads γ empty *) + \pre std_recursive_mutex.used_threads γ {[thr]} + \pre std_recursive_mutex.acquireable (TT := TT) γ thr std_recursive_mutex.NotHeld (P this) \post emp). Lemma C_dtor_ok : - (* We only get [|> recursive_mutex.dtor_spec'], and that's not enough *) - recursive_mutex.dtor_spec' |-- verify[source] "C::~C()". + (* We only get [|> std_recursive_mutex.std_dtor_spec], and that's not enough *) + std_recursive_mutex.std_dtor_spec |-- verify[source] "C::~C()". Proof. verify_shift; go. - wapply (recursive_mutex.logout thr _ ∅); first by set_solver. - rewrite recursive_mutex.acquireable.unlock /= (left_id_L _ (∪)). + wapply (std_recursive_mutex.logout_acquirable (TT := TT) thr _ ∅ (P this)); first by set_solver. + rewrite /= (left_id_L _ (∪)). go with br_erefl. iModIntro. go. progress destruct_tele; rewrite P.unlock /=. go. - iApply affine; [apply mpred_BiAffine|go]. Qed. cpp.spec "C::one_answer()" from source inline. @@ -187,18 +178,18 @@ Section with_cpp. iExists ?[K], (mk 42). go. iSplitL ""; [by go | go]. - have [? [??]] : exists a, n = 1%nat /\ recursive_mutex.acquire a (recursive_mutex.release (recursive_mutex.Held n (mk 42))). { + have [? [??]] : exists a, n = 1%nat /\ std_recursive_mutex.acquire a (std_recursive_mutex.release (std_recursive_mutex.Held n (mk 42))). { lazymatch goal with - | _ : recursive_mutex.Held ?x _ = _ |- _ => rename x into n0 + | _ : std_recursive_mutex.Held ?x _ = _ |- _ => rename x into n0 end. (* This step breaks abstractions, but we have taken the lock yet hints don't give us access to the resouce. *) assert (n0 = 0 /\ n = 1)%nat as [-> ->]. { - rewrite-> recursive_mutex.release.unlock in *. + rewrite-> std_recursive_mutex.release.unlock in *. destruct n0, n; naive_solver. } - rewrite recursive_mutex.release.unlock /=. - exists recursive_mutex.NotHeld. + rewrite std_recursive_mutex.release.unlock /=. + exists std_recursive_mutex.NotHeld. split; first done. typeclasses eauto with br_hints. } @@ -206,8 +197,8 @@ Section with_cpp. rewrite CR'.unlock. destruct args as [a1 []]. go. - have [Ha1 Hrel]: (a1 = 42 /\ recursive_mutex.release (recursive_mutex.Held n (mk a1)) = recursive_mutex.NotHeld). { - rewrite-> recursive_mutex.release.unlock in *; naive_solver. + have [Ha1 Hrel]: (a1 = 42 /\ std_recursive_mutex.release (std_recursive_mutex.Held n (mk a1)) = std_recursive_mutex.NotHeld). { + rewrite-> std_recursive_mutex.release.unlock in *; naive_solver. } subst a1. iExists ?[K], (mk 42). rewrite Hrel /=. From 4ed75f329dd387a7740b1df48a4a59db9c588b69 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 02:49:36 +0000 Subject: [PATCH 045/109] add [transfer_with_mutex.cpp] and proof as a client example of the mutex library; update recursive_mutex specs with better abstraction transfer_with_mutex.cpp shows that the concurrent bank account data structure [C]'s rep predicate [accountR] only exposes the namespace [N] of the underlying invariant, and is not tied to a specific thread. The thread can then make a handle [account_acquireableR] with a fraction of [accountR] and its own [my_mutexes _ _ N]. --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 78 +++ .../proof/mutex/spec/recursive_mutex.v | 181 +++--- .../proof/mutex/spec/recursive_mutex_atomic.v | 368 ++++++------ .../test/mutex/transfer_with_mutex.cpp | 62 ++ .../mutex/transfer_with_mutex_cpp_proof.v | 533 ++++++++++++++++++ 5 files changed, 981 insertions(+), 241 deletions(-) create mode 100644 rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 95424f65..fa6c5787 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -52,11 +52,28 @@ Module Type MUTEX_SETS. mutex_set_map γ T |-- (|==> mutex_set_map γ (T ∪ {[th]}) ** my_mutexes γ th (CoPset ⊤)). + Parameter mutex_sets_free_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, + th ∈ T -> + mutex_set_map γ T ** my_mutexes γ th (CoPset ⊤) |-- + mutex_set_map γ (T \ {[th]}). + Parameter mutex_set_map_empty_no_my_mutexes : + forall `{Σ : cpp_logic, !G Σ} γ th (N : namespace), + mutex_set_map γ ∅ ** my_mutexes γ th (CoPset (↑N)) |-- False. Parameter my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} γ th (E N : coPset), N ⊆ E -> my_mutexes γ th (CoPset E) |-- my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). + Parameter my_mutexes_split : forall `{Σ : cpp_logic, !G Σ} + γ th (E1 E2 : coPset), + E1 ## E2 -> + my_mutexes γ th (CoPset (E1 ∪ E2)) ⊣⊢ + my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2). + Parameter my_mutexes_join_mutex_name : forall `{Σ : cpp_logic, !G Σ} + γ th (E N : coPset), + N ⊆ E -> + my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N) |-- + my_mutexes γ th (CoPset E). (* an example that two threads can allocate the same namespace token. *) Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !G Σ} @@ -258,6 +275,45 @@ Module MutexSets : MUTEX_SETS. iModIntro. iFrame. Qed. + Lemma mutex_sets_free_thread γ T th : + th ∈ T -> + mutex_set_map γ T ** my_mutexes γ th (CoPset ⊤) |-- + mutex_set_map γ (T \ {[th]}). + Proof. + intros Hmember. + rewrite /mutex_set_map /my_mutexes -own_op. + have Heq : reserve T ⋅ discrete_fun_singleton th (CoPset ⊤) ≡ + reserve (T \ {[th]}). + { intros th'. + rewrite discrete_fun_lookup_op. + destruct (decide (th = th')) as [<-|Hne]. + - rewrite discrete_fun_lookup_singleton /reserve. + rewrite decide_True; last done. + rewrite decide_False; last set_solver. + by rewrite left_id. + - rewrite discrete_fun_lookup_singleton_ne; last done. + rewrite right_id /reserve. + destruct (decide (th' ∈ T)). + + rewrite !decide_True; try set_solver. + + rewrite !decide_False; try set_solver. + } + by rewrite Heq. + Qed. + + Lemma mutex_set_map_empty_no_my_mutexes γ th (N : namespace) : + mutex_set_map γ ∅ ** my_mutexes γ th (CoPset (↑N)) |-- False. + Proof. + rewrite /mutex_set_map /my_mutexes. + iIntros "[Hmap Hmy]". + iDestruct (own_valid_2 with "Hmap Hmy") as %Hvalid. + iPureIntro. + specialize (Hvalid th). + rewrite discrete_fun_lookup_op discrete_fun_lookup_singleton /reserve in Hvalid. + rewrite coPset_disj_valid_op in Hvalid. + have Hnonempty := nclose_non_empty N. + set_solver. + Qed. + Lemma my_mutexes_alloc_mutex_name γ th (E N : coPset) : N ⊆ E -> my_mutexes γ th (CoPset E) |-- @@ -271,6 +327,28 @@ Module MutexSets : MUTEX_SETS. done. Qed. + Lemma my_mutexes_split γ th (E1 E2 : coPset) : + E1 ## E2 -> + my_mutexes γ th (CoPset (E1 ∪ E2)) ⊣⊢ + my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2). + Proof. + intros Hdisjoint. + rewrite /my_mutexes -own_op discrete_fun_singleton_op. + by rewrite coPset_disj_union. + Qed. + + Lemma my_mutexes_join_mutex_name γ th (E N : coPset) : + N ⊆ E -> + my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N) |-- + my_mutexes γ th (CoPset E). + Proof. + intros Hsub. + rewrite -my_mutexes_split; last set_solver. + rewrite difference_union_L. + have -> : E ∪ N = E by set_solver. + done. + Qed. + End theory. (* an example that two threads can allocate the same namespace token. *) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 23bfc9a7..b53c696a 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -1,4 +1,5 @@ Require Import iris.algebra.gset. +Require Import iris.algebra.coPset. Require Import iris.algebra.lib.excl_auth. Require Import skylabs.bi.tls_modalities. @@ -9,7 +10,7 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.requirements. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Import linearity. @@ -97,49 +98,71 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Existing Class G. #[global] Arguments G {_ _} Σ : assert. - Parameter used_threads : forall `{Σ : cpp_logic, !G Σ} - {HAS_THREADS : HasStdThreads Σ}, gname -> gset thread_idT -> mpred. + (** A pool is shared by mutexes; each mutex reserves its own namespace. *) + Parameter pool_name : gname -> iprop.gname. + Parameter rmutex_inv_namespace : gname -> namespace. + #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. + Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. + #[global] Declare Instance token_fractional + `{Σ : cpp_logic, !G Σ} g : CFractional (token g). + #[global] Declare Instance token_timeless + `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). + Parameter held_token : forall `{Σ : cpp_logic, !G Σ}, - gname -> thread_idT -> nat -> mpred. - (* [acquireable γ th s P] contains an acquire state [s] and, if the acquire - state is held, the resource P. *) - Parameter acquireable : forall `{Σ : cpp_logic, !G Σ} - {HAS_THREADS : HasStdThreads Σ}, gname -> thread_idT -> - forall {TT : tele}, acquire_state TT -> (TT -t> mpred) -> mpred. + gname -> cQp.t -> thread_idT -> nat -> mpred. + + Definition acquireable `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} (g : gname) (qt : cQp.t) (th : thread_idT) + {TT : tele} (s : acquire_state TT) (P : TT -t> mpred) : mpred := + current_thread th ** + match s with + | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑rmutex_inv_namespace g) + | Held n xs => held_token g qt th n ** @tele_app TT (fun _ => mpred) P xs + end. (* abstract predicate of owership of a recursive mutex that protects some resource [P], e.g. in the form of [inv _ P]. Note that it only represents the mutex data structure, not the ownership of [P]. *) Parameter R : forall `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque used_threads held_token acquireable R : sl_opacity typeclass_instances. + #[global] Hint Opaque token held_token acquireable R : sl_opacity typeclass_instances. Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Declare Instance held_token_timeless g th n : - Timeless (held_token g th n). - #[global] Declare Instance acquireable_current_thread : - `{Observe (current_thread th) (acquireable g th (TT := TT) t P)}. - - Axiom acquireable_Held : forall g th {TT : tele} n (xs : TT) P, - acquireable g th (Held n xs) P ⊣⊢ - current_thread th ** held_token g th n ** @tele_app TT (fun _ => mpred) P xs. - - Axiom use_thread_acquirable : forall {TT : tele} th g m P, - th ∉ m -> - current_thread th ** used_threads g m |-- (|==> - used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P). - - Axiom logout_acquirable : forall {TT : tele} th g m P, - th ∉ m -> - used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P |-- - (|==> used_threads g m). + #[global] Declare Instance held_token_timeless g qt th n : + Timeless (held_token g qt th n). + #[global] Instance acquireable_current_thread : + `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. + Proof. rewrite /acquireable; apply _. Qed. + + (** Registration consumes a token share and the namespace permission split + from the thread's pool. Unregistering returns both resources. *) + Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) + {TT : tele} (P : TT -t> mpred) : + current_thread th ** token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) |-- + acquireable g qt th NotHeld P. + Proof. by rewrite /acquireable. Qed. + + Lemma unregister_thread (g : gname) (qt : cQp.t) (th : thread_idT) + {TT : tele} (P : TT -t> mpred) : + acquireable g qt th NotHeld P |-- + current_thread th ** token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). + Proof. by rewrite /acquireable. Qed. Context {σ : genv}. #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). + + (** Owning any held_token (which implies [th] holds the lock) contradicts + resources needed to destruct the lock. *) + Parameter locked_contradict_full_token : forall (this : ptr) g q qt th n P, + this |-> R g q P ** token g 1$m ** held_token g qt th n |-- + (|={⊤}=> False). End with_cpp. End RECURSIVE_MUTEX_PREDS. @@ -154,45 +177,46 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this \persist{th} current_thread th - \pre{TT P xs} |> @tele_app TT (fun _ => mpred) P xs + \pre{pool N TT P xs} |> @tele_app TT (fun _ => mpred) P xs \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) \post Exists g, - this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** used_threads g empty). + [| pool_name g = pool /\ rmutex_inv_namespace g = N |] ** + this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** token g 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := (\this this \pre{g TT P} this |-> R g 1 (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre used_threads g empty + \pre token g 1$m \post |> (Exists xs, @tele_app TT (fun _ => mpred) P xs)). Definition lock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{th n} acquireable g th n P - \post Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P). + \pre{qt th n} acquireable g qt th n P + \post Exists n', [| acquire n n' |] ** ▷ acquireable g qt th n' P). Definition unlock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{th n args} acquireable g th (Held n args) P - \post acquireable g th (release $ Held n args) P). + \pre{qt th n args} acquireable g qt th (Held n args) P + \post acquireable g qt th (release $ Held n args) P). Definition do_lock (lk : gname * mpred) (K : mpred) : mpred := - ∃ TT P th n, + ∃ TT P qt th n, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** - acquireable lk.1 th n P ** + acquireable lk.1 qt th n P ** ((* TODO readd *) (* ▷ *) - (Exists n', [| acquire n n' |] ** ▷ acquireable lk.1 th n' P) -* K). + (Exists n', [| acquire n n' |] ** ▷ acquireable lk.1 qt th n' P) -* K). #[global] Arguments do_lock /. Definition do_unlock (lk : gname * mpred) (K : mpred) : mpred := - ∃ TT P th n args, + ∃ TT P qt th n args, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** - acquireable lk.1 th (Held n args) P ** + acquireable lk.1 qt th (Held n args) P ** ((* TODO readd *) (* ▷ *) - acquireable lk.1 th (release $ Held n args) P -* K). + acquireable lk.1 qt th (release $ Held n args) P -* K). #[global] Arguments do_unlock /. #[global] Instance recursive_mutex_basic_lockable : BasicLockable @@ -210,18 +234,18 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Proof. unfold lock_spec, lock_spec_alt, lock_basic_lockable, do_lock. cbn. iSplit. - - iIntros "H". iDestruct "H" as (g TT P q th n) + - iIntros "H". iDestruct "H" as (g TT P q qt th n) "(%Hxs & HR & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), - (Exists n', [| acquire n n' |] ** ▷ acquireable g th n' P)%I. + (Exists n', [| acquire n n' |] ** ▷ acquireable g qt th n' P)%I. iFrame "HR HK". iSplit; first done. - iExists TT, P, th, n. iFrame "HA". + iExists TT, P, qt, th, n. iFrame "HA". iSplit; first done. iIntros "$". - iIntros "H". iDestruct "H" as (q [g J] K') "(%Hxs & HR & Hdo & HK)". - iDestruct "Hdo" as (TT P th n) "(%Heq & HA & Hcont)". + iDestruct "Hdo" as (TT P qt th n) "(%Heq & HA & Hcont)". simpl in *. subst J. - iExists g, TT, P, q, th, n. iFrame "HR HA". + iExists g, TT, P, q, qt, th, n. iFrame "HR HA". iSplit; first done. iIntros "[HR Hac]". iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. @@ -231,18 +255,18 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Proof. unfold unlock_spec, unlock_spec_alt, unlock_basic_lockable, do_unlock. cbn. iSplit. - - iIntros "H". iDestruct "H" as (g TT P q th n args) + - iIntros "H". iDestruct "H" as (g TT P q qt th n args) "(%Hxs & HR & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), - (acquireable g th (release $ Held n args) P). + (acquireable g qt th (release $ Held n args) P). iFrame "HR HK". iSplit; first done. - iExists TT, P, th, n, args. iFrame "HA". + iExists TT, P, qt, th, n, args. iFrame "HA". iSplit; first done. iIntros "$". - iIntros "H". iDestruct "H" as (q [g J] K') "(%Hxs & HR & Hdo & HK)". - iDestruct "Hdo" as (TT P th n args) "(%Heq & HA & Hcont)". + iDestruct "Hdo" as (TT P qt th n args) "(%Heq & HA & Hcont)". simpl in *. subst J. - iExists g, TT, P, q, th, n, args. iFrame "HR HA". + iExists g, TT, P, q, qt, th, n, args. iFrame "HR HA". iSplit; first done. iIntros "[HR Hac]". iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. @@ -267,89 +291,96 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) (** The mutex representation remembers the resource after clients open it. *) #[global] Instance R_acquireable_learn_TT (this : ptr) : `{Learnable (this |-> R g q (∃ xs : tele_arg TT1, @tele_app TT1 (fun _ => mpred) P1 xs)) - (acquireable g th (TT := TT2) t P2) + (acquireable g qt th (TT := TT2) t P2) [TT2 = TT1] }. Proof. solve_learnable. Qed. #[global] Instance R_acquireable_learn_P TT (this : ptr) : `{Learnable (this |-> R g q (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P1 xs)) - (acquireable g th (TT := TT) t P2) + (acquireable g qt th (TT := TT) t P2) [P2 = P1] }. Proof. solve_learnable. Qed. #[global] Instance acquireable_learn_TT : `{Learnable - (acquireable g th (TT := TT1) t1 P1) - (acquireable g th (TT := TT2) t2 P2) - [TT2 = TT1] }. + (acquireable g qt1 th (TT := TT1) t1 P1) + (acquireable g qt2 th (TT := TT2) t2 P2) + [qt2 = qt1; TT2 = TT1] }. Proof. solve_learnable. Qed. #[global] Instance acquireable_learn γ th TT : - LearnEq2 (acquireable γ th (TT := TT)). + LearnEq3 (fun qt s P => acquireable γ qt th (TT := TT) s P). Proof. solve_learnable. Qed. - #[global] Instance held_token_learn γ th : LearnEq1 (held_token γ th). + #[global] Instance held_token_learn γ th : LearnEq2 (fun qt n => held_token γ qt th n). Proof. solve_learnable. Qed. #[global] Instance later_acquireable_learn γ th TT : - LearnEq2 (fun a b => bi_later (acquireable γ th (TT := TT) a b)). + LearnEq3 (fun qt a b => bi_later (acquireable γ qt th (TT := TT) a b)). Proof. solve_learnable. Qed. #[global] Instance : `{Learnable (current_thread th) - (acquireable (TT := TT0) γ th0 args P0) + (acquireable (TT := TT0) γ qt th0 args P0) [th0 = th] }. Proof. solve_learnable. Qed. #[global] Instance learn_args {TT : tele} (t : acquire_state TT) (P : TT -t> mpred) : `{Learnable - (@tele_app TT (fun _ => mpred) P args ** held_token γ th n) - (acquireable γ th t P) - [t = Held n args] }. + (@tele_app TT (fun _ => mpred) P args ** held_token γ qt th n) + (acquireable γ qt' th t P) + [qt' = qt; t = Held n args] }. + Proof. solve_learnable. Qed. + + #[global] Instance held_token_acquireable_learn {TT : tele} : `{Learnable + (held_token g qt th n) + (acquireable (TT := TT) g qt' th (Held n' args) P) + [qt' = qt; n' = n] }. Proof. solve_learnable. Qed. Definition acquireable_current_thread_F := ltac:(mk_obs_fwd acquireable_current_thread). #[program] - Definition acquireable_is_acquired_C {TT} g th t t' P + Definition acquireable_is_acquired_C {TT} g qt th t t' P (_ : acquire (TT := TT) t t') := \cancelx - \consuming acquireable g th t' P + \consuming acquireable g qt th t' P \deduce{args} @tele_app TT (fun _ => mpred) P args \deduce{n} [| t' = Held n args /\ t = release t' |] - \deduce held_token g th n + \deduce held_token g qt th n \end. Next Obligation. intros * (? & ? & -> & ->)%is_held. - rewrite acquireable_Held. ego. + rewrite /acquireable /=. ego. Qed. #[program] Definition acquireable_acquireable_C γ := \cancelx - \consuming{th n TT args P} acquireable (TT := TT) γ th (Held n args) P + \consuming{qt th n TT args P} acquireable (TT := TT) γ qt th (Held n args) P \bound P' - \bound_existential th' args' - \proving acquireable γ th' args' P' + \bound_existential qt' th' args' + \proving acquireable γ qt' th' args' P' + \instantiate qt' := qt \instantiate th' := th \instantiate args' := Held n args \deduce @tele_app TT (fun _ => mpred) P args \through @tele_app TT (fun _ => mpred) P' args \end. - Next Obligation. intros. rewrite acquireable_Held; work; rewrite acquireable_Held; work. Qed. + Next Obligation. intros. rewrite /acquireable /=; work. Qed. #[program] - Definition own_P_is_acquireable_C {TT} g n P := + Definition own_P_is_acquireable_C {TT} g qt n P := \cancelx \preserving{th} current_thread th - \consuming held_token g th n + \consuming held_token g qt th n \bound n' args - \proving acquireable (TT := TT) g th (Held n' args) P + \proving acquireable (TT := TT) g qt th (Held n' args) P \through @tele_app TT (fun _ => mpred) P args \through [| n' = n |] \end. Next Obligation. intros. iIntros "[#Hth Htoken]" (n' args) "[HP %Heq]". - subst n'. rewrite acquireable_Held. iFrame "#∗". + subst n'. rewrite /acquireable /=. iFrame "#∗". Qed. End proof_automation. End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index 4c7eb7da..14764f6a 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -1,4 +1,5 @@ Require Import iris.algebra.gset. +Require Import iris.algebra.coPset. Require Import iris.algebra.lib.excl_auth. Require Import skylabs.bi.tls_modalities. @@ -11,7 +12,7 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.requirements. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Export skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. @@ -30,12 +31,13 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). (* FIXME the hardcoded strings can be derived from T.cpp_ty. *) (* Not prodO thread_idTO natO. *) - (* at_specs thread that has zero, locked γ th 0 does not even know which thread has non-0. *) + (* [locked γ qt th 0] does not identify the thread holding the mutex. *) Canonical Structure phys_stateUR := authR (optionR (exclR (prodO thread_idTO natO))). - (** <> <> owns the mutex <<γ>> <> times. *) + (** [locked γ qt th n] records [n] acquisitions by [th], using token share [qt]. *) Class lockedG `{Σ : cpp_logic} := { - #[local] has_lock_ghost :: lock_ghost.lockG Σ; + #[global] sets_G :: MutexSets.G Σ; + #[global] tokens_G :: MutexTokens.G Σ; #[local] has_phys_state :: HasOwn (iPropI _Σ) phys_stateUR; #[local] has_phys_state_upd :: HasOwnUpd (iPropI _Σ) phys_stateUR; @@ -46,10 +48,30 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). Record gname : Set := MkGname { owned_count_id : iprop.gname; - locked_gname : iprop.gname; + pool_name : iprop.gname; + mutex_inv_namespace : namespace; inv_gname : iprop.gname; + token_gname : iprop.gname; }. + Definition token `{Σ : cpp_logic, !lockedG Σ} (g : gname) (qt : cQp.t) : mpred := + MutexTokens.token g.(token_gname) qt. + Definition given_token `{Σ : cpp_logic, !lockedG Σ} (g : gname) (qt : cQp.t) : mpred := + MutexTokens.given_token g.(token_gname) qt. + #[global] Instance token_timeless `{Σ : cpp_logic, !lockedG Σ} g qt : + Timeless (token g qt). + Proof. rewrite /token. apply _. Qed. + #[global] Instance token_WeaklyObjective `{Σ : cpp_logic, !lockedG Σ} g qt : + WeaklyObjective (token g qt). + Proof. rewrite /token /MutexTokens.token. apply _. Qed. + #[global] Instance given_token_timeless `{Σ : cpp_logic, !lockedG Σ} g qt : + Timeless (given_token g qt). + Proof. rewrite /given_token. apply _. Qed. + #[global] Instance given_token_WeaklyObjective `{Σ : cpp_logic, !lockedG Σ} g qt : + WeaklyObjective (given_token g qt). + Proof. rewrite /given_token /MutexTokens.given_token. apply _. Qed. + #[global] Hint Opaque token given_token : sl_opacity typeclass_instances. + (** [owned_count_id_auth γ Some (th, n)] implies that the lock's count is [n + 1]. *) sl.lock Definition owned_count_id_auth `{Σ : cpp_logic, !lockedG Σ} @@ -66,82 +88,43 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). #[local] Open Scope nat_scope. - (** [locked γ th n] implies that the lock's count is [n]: see [used_threads]'s - definition and [owned_count_id_auth]'s informal contract. *) + (** For positive [n], [locked γ qt th n] records the physical lock count + through [owned_count_id_frag]. At zero it owns the namespace permission + and registration token. The first lock transfers that permission into + the physical invariant; the final unlock returns it. *) sl.lock Definition locked `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (th : thread_idT) (n : nat) : mpred := - user γ.(locked_gname) th ** + (γ : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := match n with - | 0 => owned_count_id_frag γ None - | S n => owned_count_id_frag γ (Some (th, n)) + | 0 => MutexSets.my_mutexes γ.(pool_name) th + (CoPset $ ↑γ.(mutex_inv_namespace)) ** token γ qt + | S n => owned_count_id_frag γ (Some (th, n)) ** given_token γ qt end. #[only(timeless)] derive locked. - (* TODO: we should abstract this over the ownership that is produced and - then it can be used more generally. *) - sl.lock - Definition used_threads - `{Σ : cpp_logic, !lockedG Σ, !HasStdThreads Σ} - (γ : gname) (s : gset thread_idT) : mpred := - lock_ghost.used_threads γ.(locked_gname) s. - #[only(timeless)] derive used_threads. - Section locked_with_cpp. Context `{Σ : cpp_logic}. Context `{!lockedG Σ}. Context `{!HasStdThreads Σ}. - Lemma use_thread th g s : - th ∉ s -> - used_threads g s |-- - (|==> used_threads g (s ∪ {[ th ]}) ** locked g th 0). - Proof. - rewrite used_threads.unlock locked.unlock owned_count_id_frag.unlock /=. - iIntros (Hni) "at_specs". - iMod (lock_ghost.login with "at_specs") as "[$ $]"; first done. - iApply own_unit. - Qed. - - Lemma logout th g s : - th ∉ s -> - used_threads g (s ∪ {[ th ]}) ** locked g th 0 |-- - (|==> used_threads g s ** owned_count_id_frag g None). - Proof. - rewrite used_threads.unlock locked.unlock. - iIntros (Hni) "(? & ? & $)". - iApply lock_ghost.logout; first done. - work. - Qed. - #[global] Instance owned_count_id_frag_WeaklyObjective γ om : WeaklyObjective (PROP := iPropI _) (owned_count_id_frag γ om). Proof. rewrite owned_count_id_frag.unlock. apply _. Qed. #[global] Instance - locked_WeaklyObjective γ thr n : - WeaklyObjective (PROP := iPropI _) (locked γ thr n). + locked_WeaklyObjective γ qt thr n : + WeaklyObjective (PROP := iPropI _) (locked γ qt thr n). Proof. rewrite locked.unlock. apply _. Qed. - Lemma locked_excl_same_thread g th n m : - locked g th n ** locked g th m |-- False. + Lemma locked_excl_different_thread g qt qt' th th' n m : + locked g qt th n ** locked g qt' th' m |-- [| n = 0 \/ m = 0 |] ** True. Proof. rewrite locked.unlock. - work. - iDestruct (user_unique with "[$]") as "[]". - Qed. - - Lemma locked_excl_different_thread g th th' n m : - locked g th n ** locked g th' m |-- [| n = 0 \/ m = 0 |] ** True. - Proof. - destruct (decide (th = th')) as [->|Hne]. { - rewrite locked_excl_same_thread. work. - } - rewrite locked.unlock. - iIntros "[[_ at_specs] [_ B]]". + iIntros "[H1 H2]". destruct n, m; try auto. iExFalso. + iDestruct "H1" as "[A _]". iDestruct "H2" as "[B _]". rewrite owned_count_id_frag.unlock. - iDestruct (own_valid_2 with "at_specs B") as %HV; exfalso. + iDestruct (own_valid_2 with "A B") as %HV; exfalso. rewrite -auth_frag_op auth_frag_valid in HV. done. Qed. @@ -209,7 +192,7 @@ cinv ( (* NOTE: Invariant used to protect resource [r] [[ - inv (r \\// exists th n, locked th (S n)) + inv (r \\// exists qt th n, locked qt th (S n)) ]] *) @@ -224,24 +207,23 @@ cinv ( Definition rmutex_N : namespace := nroot .@@ "std" .@@ "recursive_mutex" .@ "raw_inv". - (* recursive mutex -- ownership of the class. *) - sl.lock - Definition I `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) : Rep := - type_ptrR T.cpp_ty ** - cinv rmutex_N γ.(inv_gname) (∃ owner count, rawR owner count ** - (* We use [Nat.pred] because [owned_count_id_auth] stores [counter - 1]. *) - pureR (owned_count_id_auth γ ((λ t, (t, Nat.pred count)) <$> owner))). - (* TODO: readd [|owner = None <-> count = O|] elsewhere, as sequential invariant in [R]. *) - #[only(knowledge (*,type_ptr="std::recursive_mutex"*) )] derive I. - sl.lock Definition R `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := type_ptrR T.cpp_ty ** - (* TODO: add here sequential ownership of the lock, and maybe replace I by the lock invariant. - Something like *) - (* _mutex_field |-> mutex.R q ... ** *) + as_Rep (fun this => cinv rmutex_N γ.(inv_gname) + (∃ owner count, this |-> rawR owner count ** + (* [owned_count_id_auth] stores [counter - 1]. *) + owned_count_id_auth γ ((λ t, (t, Nat.pred count)) <$> owner) ** + match owner with + | None => MutexTokens.token_full γ.(token_gname) + | Some th => MutexTokens.token_not_full γ.(token_gname) ** + MutexSets.my_mutexes γ.(pool_name) th + (CoPset $ ↑γ.(mutex_inv_namespace)) + end)) ** pureR (cinv_own γ.(inv_gname) q). - #[only(cfracsplittable)] derive R. + (* TODO: add sequential ownership of the physical lock and + [|owner = None <-> count = O|]. *) + #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Instance R_type_ptr `{Σ : cpp_logic, σ : genv, !lockedG Σ} γ q : Observe (type_ptrR T.cpp_ty) (R γ q). Proof. rewrite R.unlock. apply _. Qed. @@ -252,37 +234,60 @@ cinv ( Context {HAS_THREADS : HasStdThreads Σ}. Context `{!lockedG Σ}. - #[global] Instance I_learn : Cbn (Learn (learn_eq ==> learn_hints.fin) I). - Proof. solve_learnable. Qed. #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) R). Proof. solve_learnable. Qed. Definition ctor_spec_body : ptr -> WpSpec mpred val val := (\this this - \post Exists g, this |-> R g 1$m ** used_threads g empty). + \pre{pool N} emp + \post Exists g, + [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** + this |-> R g 1$m ** token g 1$m). Definition dtor_spec_body : ptr -> WpSpec mpred val val := (\this this \pre{g} this |-> R g 1$m - \pre used_threads g empty + \pre token g 1$m \post emp). Definition lock_spec_body : ptr -> WpSpec mpred val val := (\this this \prepost{q g} this |-> R g q (* part of both pre and post *) \persist{th} current_thread th - \pre{Q} AC << ∀ n , locked g th n >> @ top \ ↑ mask , empty - << locked g th (S n) , COMM Q >> + \pre{qt Q} AC << ∀ n , locked g qt th n >> @ top \ ↑ mask , empty + << locked g qt th (S n), COMM Q >> \post Q). Definition unlock_spec_body : ptr -> WpSpec mpred val val := (\this this \prepost{q g} this |-> R g q (* part of both pre and post *) \persist{th} current_thread th - \pre{Q} AC << ∀ n , locked g th (S n) >> @ top \ ↑ mask , empty - << locked g th n , COMM Q >> + \pre{qt Q} AC << ∀ n , locked g qt th (S n) >> @ top \ ↑ mask , empty + << locked g qt th n, COMM Q >> \post Q). + Lemma locked_contradict_full_token (this : ptr) g q qt th n {E : coPset} : + ↑rmutex_N ⊆ E -> + this |-> R g q ** token g 1$m ** locked g qt th (S n) |-- + (|={E}=> False). + Proof. + intros HE. + rewrite R.unlock locked.unlock /token !_at_sep + !_at_pureR _at_as_Rep. + iIntros "((_ & #HI & Hown) & Htoken & Hcount & _)". + iInv rmutex_N as "[(%owner & %count & _ & >Hauth & Hbalance) Hown]". + destruct owner as [owner|]. + - iDestruct "Hbalance" as "[Hbalance _]". + iAssert (▷ False)%I with "[Hbalance Htoken]" as "Hfalse". + { iNext. iApply MutexTokens.token_not_full_full_token. iFrame. } + iMod "Hfalse" as %[]. + - iEval (rewrite owned_count_id_auth.unlock /=) in "Hauth". + iEval (rewrite owned_count_id_frag.unlock) in "Hcount". + iDestruct (own_valid_2 with "Hauth Hcount") as %Hvalid. + exfalso. move: Hvalid. rewrite auth_both_valid_discrete /=. + rewrite option_included. naive_solver. + Qed. + End base_construction. End RecursiveMutexAtomicSpecs. @@ -303,12 +308,19 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. ; cinv_gname : iprop.gname }. Definition gname := rmutex_gname. + Definition pool_name (g : gname) := at_specs.pool_name g.(lock_gname). + Definition rmutex_inv_namespace (g : gname) := + at_specs.mutex_inv_namespace g.(lock_gname). - Canonical Structure cmraR := excl_authR (prodO natO thread_idTO). + (** Remember the acquired share as well as the count and owner, so the + final unlock returns the same share that was registered. *) + Canonical Structure cmraR := + excl_authR (prodO (prodO natO thread_idTO) (leibnizO cQp.t)). Class recursive_mutexG `{Σ : cpp_logic} := { #[global] locked_G :: at_specs.lockedG Σ; #[global] level_own :: HasOwn (iPropI _Σ) cmraR; + #[global] level_valid :: HasOwnValid (iPropI _Σ) cmraR; }. Definition G := @recursive_mutexG. Existing Class G. @@ -316,6 +328,8 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. #[global] Instance recursive_mutex_G `{Σ : cpp_logic} (H : G Σ) : @recursive_mutexG _ _ Σ := H. + #[global] Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ := _. + Definition rmutex_namespace := nroot .@@ "std" .@@ "recursive_mutex" .@@ "derived". @@ -323,32 +337,19 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Definition inv_rmutex `{Σ : cpp_logic, !G Σ} (g : gname) (P : mpred) : mpred := cinv rmutex_namespace g.(cinv_gname) - (Exists n th, own g.(level_gname) (●E (n, th)) ** + (Exists n th qt, own g.(level_gname) (●E (n, th, qt)) ** match n with - | 0 => P ** own g.(level_gname) (◯E (n, th)) - | S n => at_specs.locked g.(lock_gname) th (S n) + | 0 => P ** own g.(level_gname) (◯E (n, th, qt)) + | S n => at_specs.locked g.(lock_gname) qt th (S n) end). #[only(knowledge)] derive inv_rmutex. - (** Fractional ownership of the physical recursive mutex and of the - cancellable invariant that protects the custom resource P. - The two fractions do not have to be the same, we just choose to make them - equal for convenience. - *) - sl.lock - Definition derivedR `{Σ : cpp_logic, σ : genv, !G Σ} - (g : gname) (q : cQp.t) : Rep := - at_specs.R g.(lock_gname) q ** - pureR (cinv_own g.(cinv_gname) q). - #[only(cfracsplittable)] derive derivedR. - #[global] Instance derivedR_type_ptr `{Σ : cpp_logic, σ : genv, !G Σ} g q : - Observe (type_ptrR cpp_ty) (derivedR g q). - Proof. rewrite derivedR.unlock /cpp_ty. apply _. Qed. - - (** Public ownership packages the physical handle with its resource protocol. *) + (** Fractional ownership of the physical mutex, its invariant lifetime, + and the protected resource protocol. *) Definition R `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} (g : gname) (q : cQp.t) (P : mpred) : Rep := - derivedR g q ** pureR (inv_rmutex g P). + at_specs.R g.(lock_gname) q ** + pureR (cinv_own g.(cinv_gname) q) ** pureR (inv_rmutex g P). #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Instance R_type_ptr `{Σ : cpp_logic, !G Σ} @@ -356,25 +357,31 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Observe (type_ptrR cpp_ty) (R g q P). Proof. rewrite /R. apply _. Qed. - Definition used_threads `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} - (g : gname) (threads : gset thread_idT) : mpred := - at_specs.used_threads g.(lock_gname) threads. - #[global] Hint Opaque used_threads : sl_opacity typeclass_instances. - #[only(timeless)] derive used_threads. + Definition token `{Σ : cpp_logic, !G Σ} (g : gname) (qt : cQp.t) : mpred := + at_specs.token g.(lock_gname) qt. + #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} g : + CFractional (token g). + Proof. intros q1 q2. rewrite /token /at_specs.token cQp.frac_add. apply MutexTokens.token_fractional. Qed. + #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). + Proof. rewrite /token /at_specs.token. apply _. Qed. + #[global] Hint Opaque token : sl_opacity typeclass_instances. Definition held_token `{Σ : cpp_logic, !G Σ} - (g : gname) (th : thread_idT) (n : nat) : mpred := - own g.(level_gname) (◯E (S n, th)). + (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := + own g.(level_gname) (◯E (S n, th, qt)). #[global] Hint Opaque held_token : sl_opacity typeclass_instances. - #[only(timeless)] derive held_token. + #[global] Instance held_token_timeless `{Σ : cpp_logic, !G Σ} g qt th n : + Timeless (held_token g qt th n). + Proof. rewrite /held_token. apply _. Qed. Definition acquireable `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} - (g : gname) (th : thread_idT) {TT : tele} (t : acquire_state TT) + (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (t : acquire_state TT) (P : TT -t> mpred) : mpred := current_thread th ** match t with - | NotHeld => at_specs.locked g.(lock_gname) th 0 - | Held n args => held_token g th n ** tele_app P args + | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑rmutex_inv_namespace g) + | Held n args => held_token g qt th n ** tele_app P args end. #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. @@ -382,37 +389,51 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Section rules. Context `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ}. - Lemma acquireable_Held g th {TT : tele} n (args : TT) P : - acquireable g th (Held n args) P ⊣⊢ - current_thread th ** held_token g th n ** tele_app P args. - Proof. rewrite /acquireable /=. done. Qed. - #[global] Instance acquireable_current_thread : - `{Observe (current_thread th) (acquireable g th (TT := TT) t P)}. + `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. Proof. rewrite /acquireable; apply _. Qed. - Lemma use_thread_acquirable {TT} th g m P : - th ∉ m -> - current_thread th ** used_threads g m |-- (|==> - used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P). + (** Registration consumes a token share and the namespace permission split + from the thread's pool. Unregistering returns both resources. *) + Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) + {TT : tele} (P : TT -t> mpred) : + current_thread th ** token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) |-- + acquireable g qt th NotHeld P. + Proof. by rewrite /acquireable. Qed. + + Lemma unregister_thread (g : gname) (qt : cQp.t) (th : thread_idT) + {TT : tele} (P : TT -t> mpred) : + acquireable g qt th NotHeld P |-- + current_thread th ** token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). + Proof. by rewrite /acquireable. Qed. + + Context {σ : genv}. + + Lemma locked_contradict_full_token (this : ptr) g q qt th n P : + this |-> R g q P ** token g 1$m ** held_token g qt th n |-- + (|={⊤}=> False). Proof. - rewrite /used_threads /acquireable /=. - work. wapply at_specs.use_thread; first done. - work with br_erefl. iModIntro; work. + rewrite /R /token /held_token !_at_sep !_at_pureR inv_rmutex.unlock. + iIntros "((HR & Hown & #Hinv) & Htoken & Hheld)". + iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hauth & Hcase) Hown]". + iDestruct (own_valid_2 with "Hauth Hheld") as %[=]%excl_auth_agree_L; subst. + iMod "Hcase" as "Hlocked". + iMod (at_specs.locked_contradict_full_token with "[$HR $Htoken $Hlocked]") as %[]. + have Hdisjoint : (↑at_specs.rmutex_N : coPset) ## ↑rmutex_namespace. + { + rewrite /at_specs.rmutex_N /rmutex_namespace. + set base := nroot .@@ "std" .@@ "recursive_mutex". + have Hraw : base .@ "raw_inv" = base .@ (encode "raw_inv"). + { by rewrite !namespaces.ndot_unseal. } + have Hderived : base .@@ "derived" = base .@ (encode "derived"%bs). + { by rewrite !namespaces.ndot_unseal. } + rewrite Hraw Hderived. apply ndot_ne_disjoint. vm_compute. discriminate. + } + set_solver. Qed. - Lemma logout_acquirable {TT} th g m P : - th ∉ m -> - used_threads g (m ∪ {[th]}) ** acquireable (TT := TT) g th NotHeld P |-- - (|==> used_threads g m). - Proof. - rewrite /used_threads /acquireable /=. - iIntros (Hnot) "[HU [_ HL]]". - iMod (at_specs.logout th g.(lock_gname) m Hnot - with "[$HU $HL]") as "(HU & Hunit)". - iModIntro. - iFrame "HU". iApply (affine with "Hunit"). apply mpred_BiAffine. - Qed. End rules. End RecursiveMutexPreds. @@ -428,6 +449,19 @@ Section with_cpp. Context `{!G Σ}. Context `{HOV : !HasOwnValid mpredI cmraR, HOU : !HasOwnUpd mpredI cmraR}. + Local Lemma acquireable_atomic g qt th {TT : tele} (s : acquire_state TT) P : + acquireable g qt th s P ⊣⊢ + current_thread th ** + match s with + | NotHeld => at_specs.locked g.(lock_gname) qt th 0 + | Held n xs => held_token g qt th n ** tele_app P xs + end. + Proof. + destruct s; rewrite /acquireable /=; last done. + rewrite at_specs.locked.unlock /= /pool_name /rmutex_inv_namespace /token. + iSplit; iIntros "(Hth & H1 & H2)"; iFrame. + Qed. + (* basically std_ctor_atomic_spec |-- std_ctor_non_atomic_spec *) Lemma at_spec_impl_na_spec_ctor this : elaborate.spec_entails_fupd @@ -436,14 +470,15 @@ Section with_cpp. Proof using MOD HOV HOU. work. iModIntro; work. + iExists pool, N. work. rewrite /acquireable /=. - iMod (own_alloc (●E (O, th) ⋅ ◯E (O, th))) as (g) "(? & ?)". + iMod (own_alloc (●E (O, th, (1$m)%cQp) ⋅ ◯E (O, th, (1$m)%cQp))) as (g) "(? & ?)". { apply excl_auth_valid. } - wname [Preds.at_specs.used_threads] "u". wname [_ |-> _ _ _] "a". - iMod (cinv_alloc with "[-u a]") as (ginv) "(#Hinv & Hown)"; last first. + wname [at_specs.token] "Htoken". + iMod (cinv_alloc with "[-a Htoken]") as (ginv) "(#Hinv & Hown)"; last first. - iExists {| lock_gname := t; level_gname := g; cinv_gname:= ginv |}. - rewrite /R /used_threads _at_sep _at_pureR derivedR.unlock inv_rmutex.unlock /=. + rewrite /R /token /pool_name /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock /=. iModIntro. go with br_erefl $usenamed=true. - ework with br_erefl. @@ -456,18 +491,19 @@ Section with_cpp. (Spec.dtor_spec this). Proof using MOD HOV HOU. work. - rewrite /R /used_threads _at_sep _at_pureR derivedR.unlock inv_rmutex.unlock. + rewrite /R /token /pool_name /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock. work. - iMod (cinv_cancel with "[$] [$]") as (n th) "(>? & ?)"; [done..|]. + iMod (cinv_cancel with "[$] [$]") as (n th qt) "(>? & ?)"; [done..|]. destruct n as [|n'] eqn:?; work; first last. { - rewrite Preds.at_specs.locked.unlock /used_threads Preds.at_specs.used_threads.unlock. - wapply lock_ghost.used_threads_empty_no_not_locked; work with br_erefl. + iMod (at_specs.locked_contradict_full_token with "[$]") as %[]; first set_solver. } iModIntro. work. iModIntro. ego with br_erefl. (* _now_ we just need to leak ghost state and that's okay *) - wname [own] "L1"; wname [own] "L2"; iCombine "L1 L2" as "L". + iDestruct select (own _ (●E _)) as "L1". + iDestruct select (own _ (◯E _)) as "L2". + iCombine "L1 L2" as "L". iApply (affine with "L"). apply mpred_BiAffine. Qed. @@ -480,13 +516,13 @@ Section with_cpp. (Preds.at_specs.lock_spec_body this) (Spec.lock_spec this)). work. - rewrite /R _at_sep _at_pureR derivedR.unlock; work. - iExists q, (cinv_own g.(cinv_gname) q ** - (∃ t, [| acquire n t |] ∗ ▷ acquireable g th t P))%I. + rewrite /R !_at_sep !_at_pureR; work. + iExists q, qt, (cinv_own g.(cinv_gname) q ** + (∃ t, [| acquire n t |] ∗ ▷ acquireable g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock /acquireable /held_token. + rewrite inv_rmutex.unlock !acquireable_atomic /held_token /token. work; iAcIntro; rewrite /commit_acc/=; work. - iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". + iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]; work. 2: iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. all: work $usenamed=true; iApply fupd_mask_intro; first set_solver; @@ -499,15 +535,15 @@ Section with_cpp. rewrite bi.later_sep bi.later_exist_except_0. iDestruct "Hcases" as "(>(%args & ?) & >Hcase)". iMod (own_update_2 with "Hn Hcase") as "(Hg & ?)"; - first apply (excl_auth_update _ _ (1, th)). - wname [Preds.at_specs.locked _ th _] "Hlocked"; + first apply (excl_auth_update _ _ (1, th, qt)). + wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). work. + iExists (Held 0 args). rewrite /acquireable /held_token /=. work. - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; - first apply (excl_auth_update _ _ (S (S n), th)). - wname [Preds.at_specs.locked _ th _] "Hlocked"; + first apply (excl_auth_update _ _ (S (S n), th, qt)). + wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). work. + iExists (Held (S n) args). rewrite /acquireable /held_token /=. work. Qed. Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : @@ -519,21 +555,21 @@ Section with_cpp. (Preds.at_specs.unlock_spec_body this) (Spec.unlock_spec this)). work. - rewrite /R _at_sep _at_pureR derivedR.unlock; work. - iExists q, (cinv_own g.(cinv_gname) q ** - acquireable g th (release $ Held n args) P)%I. + rewrite /R !_at_sep !_at_pureR; work. + iExists q, qt, (cinv_own g.(cinv_gname) q ** + acquireable g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock /acquireable /held_token. + rewrite inv_rmutex.unlock !acquireable_atomic /held_token /token. work; iAcIntro; rewrite /commit_acc/=; work. - iInv rmutex_namespace as "[(%n' & %th' & >Hn & Hcases) ?]" "Hclose". + iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. iMod "Hcases". iApply fupd_mask_intro; first set_solver; iIntros "Hclose'". iExists n; work $usenamed=true. iMod "Hclose'" as "_". iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; - first apply (excl_auth_update _ _ (n, th)). - rewrite release.unlock; destruct n; iFrame "#∗". + first apply (excl_auth_update _ _ (n, th, qt)). + rewrite acquireable_atomic /held_token release.unlock; destruct n; iFrame "#∗". all: iMod ("Hclose" with "[-]") as "_"; ework $usenamed=true with br_erefl; done. Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp new file mode 100644 index 00000000..8bf9d084 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp @@ -0,0 +1,62 @@ +#include +#include + +// [R g] says that the account's balance is protected by namespace g. +class C { +private: + unsigned long balance; + std::recursive_mutex mut; // protects balance + +public: + explicit C(unsigned long initial_balance) : balance(initial_balance) {} + + template + + // [| g \subset E |] + // c |-> R g ** my_mutexes th E + // { c,,balance |-> v0 } f {\exists v', c,,balance |-> v' } + void call(F f) { + std::lock_guard lg(mut); + // c,,balance |-> v0 + f(balance); + // c,,balance |-> v' + //~lock_guard() + // c |-> R g ** my_mutexes th E + } + + void print_balance() { + std::lock_guard lg(mut); + std::printf("Balance: %lu\n", balance); + } +}; + + +void transfer_seq(unsigned long& b1, unsigned long& b2, unsigned long i) { + b1 -= i; + b2 += i; +} + +// from |-> R g1 ** to |-> R g2 ** my_mutexes th E +void transfer(C& from, C& to, unsigned long i) { + from.call([&](unsigned long& from_balance) { + // from ,, balance |-> v1 + // ** to |-> R g2 + // ** my_mutexes th E\g1 + to.call([&](unsigned long& to_balance) { + // from ,, balance |-> v1 + // ** to ,, balance |-> v2 + // ** my_mutexes th (E\g1)\g2 + transfer_seq(from_balance, to_balance, i); + // from ,, balance |-> v1-i + // ** to ,, balance |-> v2+i + // ** my_mutexes th (E\g1)\g2 + }); + }); +} + +int main() { + C from{4}; + C to{2}; + transfer(from, to, 3); + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v new file mode 100644 index 00000000..7e363ef3 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -0,0 +1,533 @@ +Require Import skylabs.auto.cpp.prelude.proof. +Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Require Import skylabs.brick.libstdcpp.test.mutex.transfer_with_mutex_cpp. + +Import linearity. + +(** std::lock_guard specs. + TODO remove this when lock_guard works. *) +Module transfer_lock_guard. + +sl.lock +Definition R `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (mp : ptr) (g : std_recursive_mutex.gname) (q : cQp.t) (P : mpred) : Rep := + structR "std::lock_guard" 1$m ** + _field "std::lock_guard::_M_device" + |-> refR<"std::recursive_mutex"> 1$m mp ** + pureR (mp |-> std_recursive_mutex.R g q P). +#[only(type_ptr)] derive R. +#[only(lazy_unfold)] derive R. + +Section with_cpp. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!std_recursive_mutex.G Σ}. + + #[local] Hint Opaque std_recursive_mutex.do_lock std_recursive_mutex.do_unlock : sl_opacity. + + #[global] Instance R_learn : + Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + + cpp.spec "std::lock_guard::lock_guard(std::recursive_mutex&)" + as ctor_spec from source with ( + \this this + \arg{mp} "m" (Vptr mp) + \pre{g q P} mp |-> std_recursive_mutex.R g q P + \pre{K} std_recursive_mutex.do_lock (g, P) K + \post this |-> R mp g q P ** K + ). + + cpp.spec "std::lock_guard::~lock_guard()" + as dtor_spec from source with ( + \this this + \pre{mp g q P} this |-> R mp g q P + \pre{K} std_recursive_mutex.do_unlock (g, P) K + \post mp |-> std_recursive_mutex.R g q P ** K + ). + + Lemma ctor_ok : verify[source] ctor_spec. + Proof. verify_spec; go. iExists (std_recursive_mutex.Held n args). go. Qed. + + Lemma dtor_ok : verify[source] dtor_spec. + Proof. verify_spec. rewrite !R.unlock. go. Qed. +End with_cpp. + +End transfer_lock_guard. + +Definition balance_args : tele := [tele (_ : Z)]. +Definition balance_arg (n : Z) : balance_args := + {| tele_arg_head := n; tele_arg_tail := () |}. + +(* sequential ownership of balance *) +sl.lock +Definition account_balanceR `{Σ : cpp_logic, σ : genv} (n : Z) : Rep := + _field "C::balance" |-> ulongR 1$m n. +#[only(lazy_unfold(export),timeless)] derive account_balanceR. + +(* knowledge of [this] and [this->mutex] *) +sl.lock +Definition account_mutexR + `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (g : std_recursive_mutex.gname) (q : cQp.t) : Rep := + structR "C" q ** + as_Rep (fun this : ptr => + this ,, _field "C::mut" |-> std_recursive_mutex.R g q + (∃ args : balance_args, + @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)). +#[only(cfractional,ascfractional,type_ptr,lazy_unfold(export))] derive account_mutexR. + +(** account_mutexR and balanceR protected by the mutex *) +sl.lock +Definition accountR + `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (gpool : iprop.gname) (N : namespace) (q : cQp.t) : Rep := + Exists g, + account_mutexR g q ** pureR (std_recursive_mutex.token g q) ** + pureR [| std_recursive_mutex.pool_name g = gpool /\ + std_recursive_mutex.rmutex_inv_namespace g = N |]. +#[only(type_ptr="C")] derive accountR. + +(** A thread's registered account handle, ready for acquisition. *) +sl.lock +Definition account_acquireableR + `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (gpool : iprop.gname) (N : namespace) (q : cQp.t) (th : thread_idT) : Rep := + Exists g, + account_mutexR g q ** + as_Rep (fun this : ptr => + std_recursive_mutex.acquireable (TT := balance_args) g q th + std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n)) ** + pureR [| std_recursive_mutex.pool_name g = gpool /\ + std_recursive_mutex.rmutex_inv_namespace g = N |]. +#[only(type_ptr="C")] derive account_acquireableR. + +(** Names of the two closure types and their instantiated [C::call] methods. *) +Definition outer_lambda : name := + Nscoped "transfer(C&, C&, unsigned long)" (Nanon 0). +Definition lambda_call (cl : name) : name := + Nscoped cl (Nop function_qualifiers.Nc OOCall [Tref Tulong]). +Definition inner_lambda : name := Nscoped (lambda_call outer_lambda) (Nanon 0). +Definition account_call (cl : name) : name := + Ninst (Nscoped "C" (Nfunction function_qualifiers.N "call" [Tnamed cl])) + [Atype (Tnamed cl)]. + +Section class_C. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!std_recursive_mutex.G Σ}. + + Lemma account_register_thread (this : ptr) gpool N q th : + current_thread th ** this |-> accountR gpool N q ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) |-- + this |-> account_acquireableR gpool N q th. + Proof. + rewrite accountR.unlock account_acquireableR.unlock !_at_exists. + iIntros "(#Hth & (%g & HR) & Hname)". + iEval (rewrite !_at_sep !_at_pureR) in "HR". + iDestruct "HR" as "(HR & Htoken & %Hg)". + destruct Hg as [Hgpool HN]. + iExists g. rewrite !_at_sep !_at_pureR !_at_as_Rep. + iFrame "HR". iSplitL "Htoken Hname"; last done. + iApply std_recursive_mutex.register_thread. + rewrite Hgpool HN. iFrame "#∗". + Qed. + + Lemma account_unregister_thread (this : ptr) gpool N q th : + this |-> account_acquireableR gpool N q th |-- + this |-> accountR gpool N q ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)). + Proof. + rewrite accountR.unlock account_acquireableR.unlock !_at_exists. + iIntros "(%g & HR)". + iEval (rewrite !_at_sep !_at_pureR !_at_as_Rep) in "HR". + iDestruct "HR" as "(HR & Hacquireable & %Hg)". + iDestruct (std_recursive_mutex.unregister_thread with "Hacquireable") + as "(_ & Htoken & Hname)". + destruct Hg as [Hgpool HN]. + rewrite Hgpool HN. iFrame "Hname". + iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. + Qed. + + #[local] Instance balanceR_learn : LearnEq2 account_balanceR. + Proof. solve_learnable. Qed. + #[local] Instance account_mutexR_learn : + Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) account_mutexR). + Proof. solve_learnable. Qed. + + #[program] Definition learn_balance_args_C (P : balance_args -t> mpred) := + \cancelx + \bound_existential args + \proving @tele_app balance_args (fun _ => mpred) P args + \exist n + \instantiate args := balance_arg n + \through @tele_app balance_args (fun _ => mpred) P (balance_arg n) + \end. + Next Obligation. work. Qed. + #[local] Hint Resolve learn_balance_args_C : br_hints. + + (** calls [f], gets post condition [Q], and change that to the desired post + condition [K]. *) + Definition balance_callback (cl : name) (f balance : ptr) (K : mpred) : mpred := + ∀ Q : mpred, (K -* Q) -* + wp_minvoke_O source Direct (Qconst (Tnamed cl)) Tvoid + (Tfunction (FunctionType Tvoid [Tref Tulong])) + (lambda_call cl) f [Unmaterialized (Tref Tulong) (Vptr balance)] None + (fun _ => Q). + + #[local] Hint Opaque balance_callback std_recursive_mutex.do_lock + std_recursive_mutex.do_unlock : sl_opacity. + + (* The internal call spec exposes the lock handle, which is supposed to be + hidden to the client of [C]. We define [call_spec_body] later to hide + that. *) + Definition account_call_internal_body (cl : name) : ptr -> WpSpec mpred val val := + (\this this + \arg{f} "f" (Vptr f) + \persist type_ptr (Tnamed cl) f + \prepost{g q} this |-> account_mutexR g q + \pre{K} std_recursive_mutex.do_lock + (g, (∃ args : balance_args, + @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)%I) + (|={⊤}=> balance_callback cl f (this ,, _field "C::balance") + (std_recursive_mutex.do_unlock + (g, (∃ args : balance_args, + @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)%I) K)) + \post K). + + cpp.spec (account_call outer_lambda) as outer_call_internal_spec from source with + (\exact Reduce (account_call_internal_body outer_lambda)). + cpp.spec (account_call inner_lambda) as inner_call_internal_spec from source with + (\exact Reduce (account_call_internal_body inner_lambda)). + + (** The callback invocation is supplied by the precondition, so verification + does not need a separate global specification for its operator(). *) + Ltac prove_account_call := + verify_spec; go; + lazymatch goal with q : cQp.t, qt : cQp.t, K : mpred |- _ => + iExists q, _; go; + wname [bi_wand] "Hcallback"; + iSplitL "Hcallback"; [iExact "Hcallback"|]; + let rec open_callback := + first [wname [balance_callback] "Hcallback"; + iMod "Hcallback" as "Hcallback" + | progress go1; open_callback] in + open_callback; go; + iEval (rewrite /balance_callback) in "Hcallback"; + iApply ("Hcallback" with "[-]"); go; + iExists q, K; go + end. + + Lemma outer_call_internal_ok : verify?[source] outer_call_internal_spec. + Proof. prove_account_call. Qed. + + Lemma inner_call_internal_ok : verify?[source] inner_call_internal_spec. + Proof. prove_account_call. Qed. + + (** call() is a wrapper for function [f] that takes the sequential ownership + of the balance and returns an updated one. *) + Definition call_spec_body (cl : name) : ptr -> WpSpec mpred val val := + (\this this + \arg{f} "f" (Vptr f) + \persist type_ptr (Tnamed cl) f + \persist{th} current_thread th + \prepost{gpool N q} this |-> account_acquireableR gpool N q th + \pre{K} ∀ v : Z, + this |-> account_balanceR v -* + balance_callback cl f (this ,, _field "C::balance") + (∃ v' : Z, this |-> account_balanceR v' ** K) + \post K). + + cpp.spec (account_call outer_lambda) as outer_call_spec from source with + (\exact Reduce (call_spec_body outer_lambda)). + cpp.spec (account_call inner_lambda) as inner_call_spec from source with + (\exact Reduce (call_spec_body inner_lambda)). + + Local Lemma account_call_refines cl this args post : + call_spec_body cl this args post |-- + account_call_internal_body cl this args post. + Proof. + rewrite /call_spec_body /account_call_internal_body. + cbn. + iIntros "H". + iDestruct "H" as (f th gpool N q K) + "(%Hargs & #Hf & #Hth & HR & Hcallback & Hpost)". + iEval (rewrite account_acquireableR.unlock _at_exists) in "HR". + iDestruct "HR" as (g) "HR". + iEval (rewrite !_at_sep !_at_pureR _at_as_Rep) in "HR". + iDestruct "HR" as "(HR & Hacquireable & %Hg)". + iExists f, g, q, + (std_recursive_mutex.acquireable (TT := balance_args) g q th + std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n) ** K)%I. + iFrame "Hf HR". iSplit; first done. + iSplitR "Hpost". + - rewrite /std_recursive_mutex.do_lock /=. + iExists balance_args, (fun n : Z => this |-> account_balanceR n)%I, + q, th, std_recursive_mutex.NotHeld. + iSplit; first done. iFrame "Hacquireable". + iIntros "Hlocked". + iDestruct "Hlocked" as (s) "[%Hac Hlocked]". + rewrite std_recursive_mutex.acquire.unlock in Hac. + destruct Hac as [[v []] ->]. + iEval (rewrite /std_recursive_mutex.acquireable /=) in "Hlocked". + iDestruct "Hlocked" as "(_ & Hheld & Hbalance)". + iMod "Hheld". iMod "Hbalance". iModIntro. + rewrite /balance_callback. iIntros (Q) "HQ". + iSpecialize ("Hcallback" $! v with "Hbalance"). + iApply ("Hcallback" with "[-]"). + iIntros "Hresult". + iDestruct "Hresult" as (v') "[Hbalance HK]". + iApply "HQ". + iExists balance_args, (fun n : Z => this |-> account_balanceR n)%I, + q, th, 0%nat, (balance_arg v'). + iSplit; first done. iSplitL "Hheld Hbalance". + { rewrite /std_recursive_mutex.acquireable /=. iFrame "#∗". } + iIntros "Hunlocked". + iEval (rewrite std_recursive_mutex.release.unlock) in "Hunlocked". + iFrame. + - iIntros "(HR & Hacquireable & HK)". + iApply "Hpost". iFrame "HK". + rewrite account_acquireableR.unlock _at_exists. iExists g. + rewrite !_at_sep !_at_pureR _at_as_Rep. + iFrame. done. + Qed. + + Local Lemma outer_call_refines : outer_call_internal_spec |-- outer_call_spec. + Proof. apply specify_mono. intros this args post. apply account_call_refines. Qed. + + Local Lemma inner_call_refines : inner_call_internal_spec |-- inner_call_spec. + Proof. apply specify_mono. intros this args post. apply account_call_refines. Qed. + + Lemma outer_call_ok : verify?[source] outer_call_spec. + Proof. work. wapply outer_call_refines. wapply outer_call_internal_ok. work. Qed. + + Lemma inner_call_ok : verify?[source] inner_call_spec. + Proof. work. wapply inner_call_refines. wapply inner_call_internal_ok. work. Qed. + + #[local] Instance balanceR_tele_timeless (this : ptr) args : + Timeless (@tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args). + Proof. destruct args as [n []]. apply _. Qed. + + Section construction. + (** FIXME this should be provable *) + Context `{balance_objective : !forall (this : ptr) args, + Objective (@tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)}. + + cpp.spec "C::C(unsigned long)" as ctor_spec from source with ( + \this this + \arg{initial_balance} "initial_balance" (Vint initial_balance) + \persist{th} current_thread th + \pre{gpool N} emp + \post this |-> accountR gpool N 1$m + ). + + Lemma ctor_ok : verify[source] ctor_spec. + Proof using Type balance_objective. + verify_spec; go. + iExists gpool, N, balance_args, (fun n : Z => this |-> account_balanceR n)%I, + (balance_arg initial_balance); go. + rewrite -bi.later_intro. + go. + rewrite accountR.unlock. go with br_erefl. + Qed. + + End construction. + + cpp.spec "C::~C()" as dtor_spec from source with ( + \this this + \pre{gpool N} this |-> accountR gpool N 1$m + \post emp + ). + + Lemma dtor_ok : std_recursive_mutex.std_dtor_spec |-- verify[source] dtor_spec. + Proof. + verify_shift; go. rewrite accountR.unlock. go with br_erefl. + match goal with + | |- context[wp_destroy_named _ _ _ ?K] => + remember K as continuation eqn:Hcontinuation + end. + iModIntro. go. rewrite Hcontinuation. + iDestruct select (bi_later (bi_exist _)) + as "Hbalance". + iApply fupd_wp_destroy_val. + iMod "Hbalance" as ([n []]) "Hbalance". + iModIntro. go $usenamed=true. + Qed. + +End class_C. + +Section clients. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{!std_recursive_mutex.G Σ}. + + #[local] Instance accountR_learn : + Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_hints.fin) accountR). + Proof. solve_learnable. Qed. + + #[local] Instance account_acquireableR_learn : + Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_eq ==> learn_hints.fin) account_acquireableR). + Proof. solve_learnable. Qed. + + cpp.spec (lambda_call outer_lambda) from source inline. + cpp.spec (lambda_call inner_lambda) from source inline. + cpp.spec (Nscoped outer_lambda Ndtor) from source inline. + cpp.spec (Nscoped inner_lambda Ndtor) from source inline. + + (* transfer balance with sequential ownership of the balances *) + cpp.spec "transfer_seq(unsigned long&, unsigned long&, unsigned long)" + as transfer_seq_spec from source with ( + \arg{b1} "b1" (Vptr b1) + \arg{b2} "b2" (Vptr b2) + \arg{i} "i" (Vint i) + \pre{v1 v2} b1 |-> ulongR 1$m v1 ** b2 |-> ulongR 1$m v2 + \post b1 |-> ulongR 1$m (trim 64 (v1 - i)) ** + b2 |-> ulongR 1$m (trim 64 (v2 + i))). + + Lemma transfer_seq_ok : verify[source] transfer_seq_spec. + Proof. verify_spec; go. Qed. + + (** Transfer balances using the two accounts' acquisition handles. *) + cpp.spec "transfer(C&, C&, unsigned long)" as transfer_spec from source with ( + \arg{from} "from" (Vptr from) + \arg{to} "to" (Vptr to) + \arg{i} "i" (Vint i) + \persist{th} current_thread th + \prepost{gpool Nfrom Nto qfrom qto} + from |-> account_acquireableR gpool Nfrom qfrom th ** + to |-> account_acquireableR gpool Nto qto th + \post emp). + + Local Lemma call_with_continuation (A : mpred -> mpred) R Q : + A (R -* Q)%I |-- ∃ K, A K ** (R ** K -* Q). + Proof. + iIntros "HA". iExists (R -* Q)%I. iFrame "HA". + iIntros "[HR HK]". iApply "HK". iFrame. + Qed. + + Lemma transfer_ok : inner_call_spec ** transfer_seq_spec |-- verify[source] transfer_spec. + Proof. + verify_shift; go. + iExists qfrom. go. + iApply call_with_continuation. + iIntros (v1) "Hfrom_balance". + (* The outer acquisition exposes from's balance; to retains its handle. *) + wname [account_acquireableR] "Hto". + iAssert (from |-> account_balanceR v1 ** to |-> account_acquireableR gpool Nto qto th)%I + with "[$Hfrom_balance $Hto]" as "Hafter_from". + iDestruct "Hafter_from" as "[? ?]". + rewrite /balance_callback. iIntros (Qfrom) "HKfrom". go. + iExists qto. go. + iApply call_with_continuation. + iIntros (v2) "Hto_balance". + (* The inner acquisition exposes both balances. *) + wname [account_balanceR] "Hfrom_balance". + iAssert (from |-> account_balanceR v1 ** to |-> account_balanceR v2)%I + with "[$Hfrom_balance $Hto_balance]" as "Hbefore_transfer". + iDestruct "Hbefore_transfer" as "[? ?]". + rewrite /balance_callback. iIntros (Qto) "HKto". go. + (* After transfer_seq: unsigned subtraction/addition. *) + iAssert (from ,, _field "C::balance" |-> ulongR 1$m (trim 64 (v1 - i)) ** + to ,, _field "C::balance" |-> ulongR 1$m (trim 64 (v2 + i)))%I + with "[$]" as "Hafter_transfer". + iDestruct "Hafter_transfer" as "[? ?]". + iApply "HKto". go. + (* Returning from to.call restores its fractional account and acquisition handle. *) + iAssert (from ,, _field "C::balance" |-> ulongR 1$m (trim 64 (v1 - i)) ** + to |-> account_acquireableR gpool Nto qto th)%I + with "[$]" as "Hafter_to". + iDestruct "Hafter_to" as "[Hfrom_balance Hto]". + iApply "HKfrom". iExists (trim 64 (v1 - i)). + iSplitL "Hfrom_balance". + { iDestruct "Hfrom_balance" as "?". go. } + iIntros "Hfrom". + (* Returning from from.call restores both fractional accounts. *) + iAssert (from |-> account_acquireableR gpool Nfrom qfrom th ** to |-> account_acquireableR gpool Nto qto th)%I + with "[$Hfrom $Hto]" as "Hafter_from". + iDestruct "Hafter_from" as "[? ?]". go. + wname [bi_wand] "Hpost". + iSpecialize ("Hpost" with "[$]"). + iModIntro. iNext. iApply "Hpost". go. + Qed. + + Lemma transfer_link : + denoteModule source ** outer_call_spec ** inner_call_spec |-- transfer_spec. + Proof. + work. wapply transfer_ok. wapply transfer_seq_ok. work. + Qed. + + cpp.spec "main()" as main_spec from source with ( + \persist{th} current_thread th + \post[Vint 0] emp + ). + + Lemma main_ok : verify[source] main_spec. + Proof. + verify_spec. + let rec allocate_pool := + first [iMod MutexSets.alloc_mutex_set_map as (gpool) "Hmap" + | progress go1; allocate_pool] in + allocate_pool. + iMod (MutexSets.mutex_sets_alloc_thread gpool ∅ th ltac:(set_solver) + with "Hmap") as "[Hmap Hnames]". + iEval (rewrite left_id_L) in "Hmap". + go. + iExists gpool, (nroot .@@ "transfer" .@ "from"); go. + iExists gpool, (nroot .@@ "transfer" .@ "to"); go. + have Hdisjoint : (↑(nroot .@@ "transfer" .@ "from") : coPset) ## + ↑(nroot .@@ "transfer" .@ "to") by solve_ndisj. + iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th ⊤ + (↑(nroot .@@ "transfer" .@ "from")) ltac:(set_solver) + with "Hnames") as "[Hnames Hfrom]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th + (⊤ ∖ ↑(nroot .@@ "transfer" .@ "from")) + (↑(nroot .@@ "transfer" .@ "to")) ltac:(set_solver) + with "Hnames") as "[Hnames Hto]". + iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "from") _) as "Hfrom_account". + iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "to") _) as "Hto_account". + iDestruct (account_register_thread with "[$Hfrom_account $Hfrom]") as "Hfrom_account"; first go. + iDestruct (account_register_thread with "[$Hto_account $Hto]") as "Hto_account"; first go. + iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". + go. iExists (1$m)%cQp, (1$m)%cQp. go. + iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "from") _ th) + as "Hfrom_account". + iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "to") _ th) + as "Hto_account". + iDestruct (account_unregister_thread with "Hfrom_account") as "[Hfrom_account Hfrom]". + iDestruct (account_unregister_thread with "Hto_account") as "[Hto_account Hto]". + iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". + go. + iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th + (⊤ ∖ ↑(nroot .@@ "transfer" .@ "from")) + (↑(nroot .@@ "transfer" .@ "to")) ltac:(set_solver) + with "[$]") as "Hnames". + iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th ⊤ + (↑(nroot .@@ "transfer" .@ "from")) ltac:(set_solver) + with "[$]") as "Hnames". + iDestruct (MutexSets.mutex_sets_free_thread gpool {[th]} th + ltac:(set_solver) with "[$]") as "Hmap". + iEval (rewrite difference_diag_L) in "Hmap". + iDestruct "Hmap" as "?". + iDestruct select (MutexSets.mutex_set_map _ _) as "Hmap". + iApply (affine with "Hmap"). apply mpred_BiAffine. + Qed. + + (* FIXME do we need this? *) + Lemma main_link `{balance_objective : !forall (this : ptr) args, + Objective (@tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)} : + denoteModule source ** std_recursive_mutex.std_ctor_spec ** std_recursive_mutex.std_dtor_spec ** + std_recursive_mutex.std_lock_spec_alt ** std_recursive_mutex.std_unlock_spec_alt |-- main_spec. + Proof. + work. + wapply main_ok. + wapply ctor_ok. + wapply dtor_ok. + wapply transfer_link. + wapply outer_call_ok. + wapply inner_call_ok. + wapply transfer_lock_guard.ctor_ok. + wapply transfer_lock_guard.dtor_ok. + work. + Qed. +End clients. From 3bc69993cb8a512bf62514f5e1072aaedd7eeef2 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 03:34:03 +0000 Subject: [PATCH 046/109] in [rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v] the atomic style recursive mutex specs is properly named as StdRecursiveMutexAtomicSpecs now --- .../test/mutex/custom_recursive_mutex_hpp_proof.v | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index 34ec407b..dfd40d9c 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -5,8 +5,8 @@ Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex_atomic. Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. -(** FIXME don't need StdAtomicSpecs *) -Module StdAtomicSpecs := RecursiveMutexAtomicSpecs StdRecursiveMutexName. +(** FIXME don't need StdRecursiveMutexAtomicSpecs *) +Module StdRecursiveMutexAtomicSpecs := RecursiveMutexAtomicSpecs StdRecursiveMutexName. Module custom_recursive_mutex. Abbreviation N := "MyRecursiveMutex"%cpp_name. @@ -24,7 +24,7 @@ Module custom_recursive_mutex. { lock_gname : mutex.gname ; cinv_gname : iprop.gname ; excl_gname : iprop.gname - ; rec_gname : StdAtomicSpecs.gname + ; rec_gname : StdRecursiveMutexAtomicSpecs.gname }. Definition lock_namespace : namespace := nroot .@@ "MyRecursiveMutex". @@ -32,8 +32,8 @@ Module custom_recursive_mutex. Parameter count_auth : ∀ `{Σ : cpp_logic} (count : nat), mpred. Parameter count_frag : ∀ `{Σ : cpp_logic} (count : nat), mpred. - (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !StdAtomicSpecs.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := - ∃ x, StdAtomicSpecs.owned_count_id_auth γ.(rec_gname) x. *) + (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !StdRecursiveMutexAtomicSpecs.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := + ∃ x, StdRecursiveMutexAtomicSpecs.owned_count_id_auth γ.(rec_gname) x. *) (* Definition rawR `{Σ : cpp_logic, σ : genv} (owner : option thread_idT) (count : nat) : Rep := structR "std::recursive_mutex" 1$m ** @@ -41,7 +41,7 @@ Module custom_recursive_mutex. Section with_Σ. Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !mutex.G Σ, - !StdAtomicSpecs.lockedG Σ}. + !StdRecursiveMutexAtomicSpecs.lockedG Σ}. Definition mutex_content (γ : gname) : Rep := ∃ count, @@ -68,7 +68,7 @@ Module custom_recursive_mutex. cinv lock_namespace γ.(cinv_gname) (∃ count owner, count_frag count ** this ,, _field "MyRecursiveMutex::m_owner" |-> atomic_thread_idT 1$m owner ** - StdAtomicSpecs.owned_count_id_auth γ.(rec_gname) ((λ t, (t, Nat.pred count)) <$> owner) ** + StdRecursiveMutexAtomicSpecs.owned_count_id_auth γ.(rec_gname) ((λ t, (t, Nat.pred count)) <$> owner) ** [| owner = None <-> count = 0 |] ** match count with | 0 => emp From da0dd57cbd31d3f262b24279d0b068e5b48a6bac Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 03:41:26 +0000 Subject: [PATCH 047/109] [mutex.v] and [recursive_mutex.v]'s register rule is bidirectional, so threads can unregister and get token back --- .../proof/mutex/demo_cpp_proof.v | 18 ++++----- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 14 +++---- .../proof/mutex/spec/recursive_mutex.v | 40 ++++++++----------- .../proof/mutex/spec/recursive_mutex_atomic.v | 39 +++++++----------- .../test/mutex/custom_mutex_hpp_proof.v | 10 ++--- .../test/mutex/double_incr_cpp_proof.v | 5 ++- .../mutex/transfer_with_mutex_cpp_proof.v | 6 +-- 7 files changed, 59 insertions(+), 73 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v index c1d5e25d..7730a964 100644 --- a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v +++ b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v @@ -50,9 +50,9 @@ Section std_recursive_mutex. Context {HAS_THREADS : HasStdThreads Σ}. Context `{!std_recursive_mutex.G Σ}. - Lemma acquireable_update_equiv {TT : tele} γ th f t1 t2 P : + Lemma acquireable_update_equiv {TT : tele} γ qt th f t1 t2 P : acquire t1 t2 -> - acquireable γ th (update f t1) P ⊣⊢ acquireable γ th (release (TT := TT) (update f t2)) P. + acquireable γ qt th (update f t1) P ⊣⊢ acquireable γ qt th (release (TT := TT) (update f t2)) P. Proof. intros. by erewrite std_recursive_mutex.update_eq. @@ -74,15 +74,15 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). cpp.spec "C::update_b(long)" as C_update_b from demo_cpp.source with (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). #[global] Instance CR_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) CR). Proof. solve_learnable. Qed. @@ -142,15 +142,15 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args th} std_recursive_mutex.acquireable γ th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). Lemma transfer_ok : verify[source] "C::transfer(int)". Proof. verify_spec; go. destruct args as [a [b []]]; work. pose (updated := mk (trim 64 (a + x)) (trim 64 (b + (0 - x)))). - iExists (std_recursive_mutex.acquireable γ th + iExists (std_recursive_mutex.acquireable γ qt th (std_recursive_mutex.release (std_recursive_mutex.Held n updated)) (P this)), updated, n. go. Qed. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 1b7e894e..dcd8c481 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -16,8 +16,8 @@ Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Import linearity. -(** A MUTEX_PREDS says a mutex spec is parametrized by some `token`, `not_locked` - and `locked`. The exact model depends on the implementation. *) +(** A MUTEX_PREDS module says a mutex spec is parametrized by some `token`, + `not_locked` and `locked`. The exact model depends on the implementation. *) Module Type MUTEX_PREDS. Parameter gname : Set. @@ -57,7 +57,7 @@ Module Type MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). - (** The thread-registration pool used by this mutex. *) + (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) Parameter pool_name : gname -> iprop.gname. Section with_cpp. @@ -122,15 +122,15 @@ Module Type MUTEX_PREDS. [| pool_name g = pool |] ** this |-> R g 1$m P ** token g 1$m). - (** Register a thread by consuming its handle for this mutex's namespace - and its token share. [my_mutexes_alloc_mutex_name] splits this handle - from the full namespace pool supplied when the thread is spawned. *) + (** A thread's handle to the mutex is equivalent to a fractional ownership + of the [mutex] and a fraction of [token], and its [my_mutexes] with the + same namespace. *) Axiom register_thread : forall (this : ptr) (g : gname) (q : cQp.t) (P : mpred) (th : thread_idT) (qt : cQp.t), this |-> R g q P ** token g qt ** MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace) |-- + (coPset.CoPset $ ↑mutex_inv_namespace) ⊣⊢ this |-> R g q P ** not_locked this g th qt. End with_cpp. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index b53c696a..3749b44b 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -108,7 +108,7 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - Parameter held_token : forall `{Σ : cpp_logic, !G Σ}, + Parameter given_token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> thread_idT -> nat -> mpred. Definition acquireable `{Σ : cpp_logic, !G Σ} @@ -118,7 +118,7 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). match s with | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) - | Held n xs => held_token g qt th n ** @tele_app TT (fun _ => mpred) P xs + | Held n xs => given_token g qt th n ** @tele_app TT (fun _ => mpred) P xs end. (* abstract predicate of owership of a recursive mutex that protects some resource [P], e.g. in the form of [inv _ P]. Note that it only represents @@ -126,42 +126,36 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Parameter R : forall `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque token held_token acquireable R : sl_opacity typeclass_instances. + #[global] Hint Opaque token given_token acquireable R : sl_opacity typeclass_instances. Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Declare Instance held_token_timeless g qt th n : - Timeless (held_token g qt th n). + #[global] Declare Instance given_token_timeless g qt th n : + Timeless (given_token g qt th n). #[global] Instance acquireable_current_thread : `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. Proof. rewrite /acquireable; apply _. Qed. - (** Registration consumes a token share and the namespace permission split - from the thread's pool. Unregistering returns both resources. *) + (** A thread's handle to the mutex is equivalent to a fractional ownership + of the [mutex] and a fraction of [token], and its [my_mutexes] with the + same namespace. *) Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (P : TT -t> mpred) : current_thread th ** token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) |-- + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ acquireable g qt th NotHeld P. Proof. by rewrite /acquireable. Qed. - Lemma unregister_thread (g : gname) (qt : cQp.t) (th : thread_idT) - {TT : tele} (P : TT -t> mpred) : - acquireable g qt th NotHeld P |-- - current_thread th ** token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). - Proof. by rewrite /acquireable. Qed. - Context {σ : genv}. #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). - (** Owning any held_token (which implies [th] holds the lock) contradicts + (** Owning any given_token (which implies [th] holds the lock) contradicts resources needed to destruct the lock. *) Parameter locked_contradict_full_token : forall (this : ptr) g q qt th n P, - this |-> R g q P ** token g 1$m ** held_token g qt th n |-- + this |-> R g q P ** token g 1$m ** given_token g qt th n |-- (|={⊤}=> False). End with_cpp. End RECURSIVE_MUTEX_PREDS. @@ -310,7 +304,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) #[global] Instance acquireable_learn γ th TT : LearnEq3 (fun qt s P => acquireable γ qt th (TT := TT) s P). Proof. solve_learnable. Qed. - #[global] Instance held_token_learn γ th : LearnEq2 (fun qt n => held_token γ qt th n). + #[global] Instance given_token_learn γ th : LearnEq2 (fun qt n => given_token γ qt th n). Proof. solve_learnable. Qed. #[global] Instance later_acquireable_learn γ th TT : LearnEq3 (fun qt a b => bi_later (acquireable γ qt th (TT := TT) a b)). @@ -325,13 +319,13 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) #[global] Instance learn_args {TT : tele} (t : acquire_state TT) (P : TT -t> mpred) : `{Learnable - (@tele_app TT (fun _ => mpred) P args ** held_token γ qt th n) + (@tele_app TT (fun _ => mpred) P args ** given_token γ qt th n) (acquireable γ qt' th t P) [qt' = qt; t = Held n args] }. Proof. solve_learnable. Qed. - #[global] Instance held_token_acquireable_learn {TT : tele} : `{Learnable - (held_token g qt th n) + #[global] Instance given_token_acquireable_learn {TT : tele} : `{Learnable + (given_token g qt th n) (acquireable (TT := TT) g qt' th (Held n' args) P) [qt' = qt; n' = n] }. Proof. solve_learnable. Qed. @@ -346,7 +340,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) \consuming acquireable g qt th t' P \deduce{args} @tele_app TT (fun _ => mpred) P args \deduce{n} [| t' = Held n args /\ t = release t' |] - \deduce held_token g qt th n + \deduce given_token g qt th n \end. Next Obligation. intros * (? & ? & -> & ->)%is_held. @@ -372,7 +366,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Definition own_P_is_acquireable_C {TT} g qt n P := \cancelx \preserving{th} current_thread th - \consuming held_token g qt th n + \consuming given_token g qt th n \bound n' args \proving acquireable (TT := TT) g qt th (Held n' args) P \through @tele_app TT (fun _ => mpred) P args diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index 14764f6a..a768a82b 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -366,13 +366,13 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Proof. rewrite /token /at_specs.token. apply _. Qed. #[global] Hint Opaque token : sl_opacity typeclass_instances. - Definition held_token `{Σ : cpp_logic, !G Σ} + Definition given_token `{Σ : cpp_logic, !G Σ} (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := own g.(level_gname) (◯E (S n, th, qt)). - #[global] Hint Opaque held_token : sl_opacity typeclass_instances. - #[global] Instance held_token_timeless `{Σ : cpp_logic, !G Σ} g qt th n : - Timeless (held_token g qt th n). - Proof. rewrite /held_token. apply _. Qed. + #[global] Hint Opaque given_token : sl_opacity typeclass_instances. + #[global] Instance given_token_timeless `{Σ : cpp_logic, !G Σ} g qt th n : + Timeless (given_token g qt th n). + Proof. rewrite /given_token. apply _. Qed. Definition acquireable `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (t : acquire_state TT) @@ -381,7 +381,7 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. match t with | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) - | Held n args => held_token g qt th n ** tele_app P args + | Held n args => given_token g qt th n ** tele_app P args end. #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. @@ -393,29 +393,20 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. Proof. rewrite /acquireable; apply _. Qed. - (** Registration consumes a token share and the namespace permission split - from the thread's pool. Unregistering returns both resources. *) Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (P : TT -t> mpred) : current_thread th ** token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) |-- + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ acquireable g qt th NotHeld P. Proof. by rewrite /acquireable. Qed. - Lemma unregister_thread (g : gname) (qt : cQp.t) (th : thread_idT) - {TT : tele} (P : TT -t> mpred) : - acquireable g qt th NotHeld P |-- - current_thread th ** token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). - Proof. by rewrite /acquireable. Qed. - Context {σ : genv}. Lemma locked_contradict_full_token (this : ptr) g q qt th n P : - this |-> R g q P ** token g 1$m ** held_token g qt th n |-- + this |-> R g q P ** token g 1$m ** given_token g qt th n |-- (|={⊤}=> False). Proof. - rewrite /R /token /held_token !_at_sep !_at_pureR inv_rmutex.unlock. + rewrite /R /token /given_token !_at_sep !_at_pureR inv_rmutex.unlock. iIntros "((HR & Hown & #Hinv) & Htoken & Hheld)". iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hauth & Hcase) Hown]". iDestruct (own_valid_2 with "Hauth Hheld") as %[=]%excl_auth_agree_L; subst. @@ -454,7 +445,7 @@ Section with_cpp. current_thread th ** match s with | NotHeld => at_specs.locked g.(lock_gname) qt th 0 - | Held n xs => held_token g qt th n ** tele_app P xs + | Held n xs => given_token g qt th n ** tele_app P xs end. Proof. destruct s; rewrite /acquireable /=; last done. @@ -520,7 +511,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** (∃ t, [| acquire n t |] ∗ ▷ acquireable g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock !acquireable_atomic /held_token /token. + rewrite inv_rmutex.unlock !acquireable_atomic /given_token /token. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]; work. @@ -538,12 +529,12 @@ Section with_cpp. first apply (excl_auth_update _ _ (1, th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). rewrite /acquireable /held_token /=. work. + iExists (Held 0 args). rewrite /acquireable /given_token /=. work. - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; first apply (excl_auth_update _ _ (S (S n), th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). rewrite /acquireable /held_token /=. work. + iExists (Held (S n) args). rewrite /acquireable /given_token /=. work. Qed. Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : @@ -559,7 +550,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** acquireable g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock !acquireable_atomic /held_token /token. + rewrite inv_rmutex.unlock !acquireable_atomic /given_token /token. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. @@ -569,7 +560,7 @@ Section with_cpp. iMod "Hclose'" as "_". iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; first apply (excl_auth_update _ _ (n, th, qt)). - rewrite acquireable_atomic /held_token release.unlock; destruct n; iFrame "#∗". + rewrite acquireable_atomic /given_token release.unlock; destruct n; iFrame "#∗". all: iMod ("Hclose" with "[-]") as "_"; ework $usenamed=true with br_erefl; done. Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index c186f8d8..3af58d1b 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -201,10 +201,10 @@ Module CustomMutexPreds <: MUTEX_PREDS. Proof. intros q1 q2. rewrite /not_locked /not_locked_ghost. iIntros "[F1 _] [F2 _]". - have Hnonempty : (↑ mutex_inv_namespace : coPset) ∩ ↑ mutex_inv_namespace ≠ ∅. - { rewrite intersection_idemp_L. apply nclose_non_empty. } + have Hoverlap : ~ ((↑ mutex_inv_namespace : coPset) ## ↑ mutex_inv_namespace). + { have Hnonempty := nclose_non_empty mutex_inv_namespace. set_solver. } iDestruct (MutexSets.my_mutexes_exclusive _ th - (↑ mutex_inv_namespace) (↑ mutex_inv_namespace) Hnonempty + (↑ mutex_inv_namespace) (↑ mutex_inv_namespace) Hoverlap with "[$F1 $F2]") as %[]. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} @@ -312,11 +312,11 @@ Module CustomMutexPreds <: MUTEX_PREDS. (th : thread_idT) (qt : cQp.t) : this |-> R g q P ** token g qt ** MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace) |-- + (coPset.CoPset $ ↑mutex_inv_namespace) ⊣⊢ this |-> R g q P ** not_locked this g th qt. Proof. rewrite /not_locked /not_locked_ghost /token /state_token /pool_name. - iIntros "($ & T & M)". iFrame. + iSplit; iIntros "($ & ? & ?)"; iFrame. Qed. End with_Σ. End CustomMutexPreds. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v index d22a4a25..b9b48daa 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -105,8 +105,9 @@ Section with_cpp. iDestruct (MutexSets.my_mutexes_alloc_mutex_name (mutex.pool_name g) child ⊤ (↑mutex.mutex_inv_namespace) ltac:(set_solver) with "Hset") as "[Hrest Hname]". - iDestruct (mutex.register_thread with "[$Hm $Htoken $Hname]") - as "[Hm Hnotlocked]". + iCombine "Hm Htoken Hname" as "Hm". + iEval (rewrite mutex.register_thread) in "Hm". + iDestruct "Hm" as "[Hm Hnotlocked]". iEval (unfold double_incr_spec_body, specify, unmaterialized_specR) in "Hcode". iApply (invoke.use_cptrR with "Hcode"). cbn. iSplitR; first done. diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index 7e363ef3..e873f963 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -130,7 +130,7 @@ Section class_C. destruct Hg as [Hgpool HN]. iExists g. rewrite !_at_sep !_at_pureR !_at_as_Rep. iFrame "HR". iSplitL "Htoken Hname"; last done. - iApply std_recursive_mutex.register_thread. + rewrite -std_recursive_mutex.register_thread. rewrite Hgpool HN. iFrame "#∗". Qed. @@ -143,8 +143,8 @@ Section class_C. iIntros "(%g & HR)". iEval (rewrite !_at_sep !_at_pureR !_at_as_Rep) in "HR". iDestruct "HR" as "(HR & Hacquireable & %Hg)". - iDestruct (std_recursive_mutex.unregister_thread with "Hacquireable") - as "(_ & Htoken & Hname)". + iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". + iDestruct "Hacquireable" as "(_ & Htoken & Hname)". destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "Hname". iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. From 2bacd213abd28830789276f2bdc91b3b5cc7f0b8 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 03:54:21 +0000 Subject: [PATCH 048/109] make the thread registration in [transfer_with_mutex_cpp_proof.v] bidirectional --- .../mutex/transfer_with_mutex_cpp_proof.v | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index e873f963..08f82d9b 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -120,34 +120,27 @@ Section class_C. Lemma account_register_thread (this : ptr) gpool N q th : current_thread th ** this |-> accountR gpool N q ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) |-- + MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) ⊣⊢ this |-> account_acquireableR gpool N q th. Proof. rewrite accountR.unlock account_acquireableR.unlock !_at_exists. - iIntros "(#Hth & (%g & HR) & Hname)". - iEval (rewrite !_at_sep !_at_pureR) in "HR". - iDestruct "HR" as "(HR & Htoken & %Hg)". - destruct Hg as [Hgpool HN]. - iExists g. rewrite !_at_sep !_at_pureR !_at_as_Rep. - iFrame "HR". iSplitL "Htoken Hname"; last done. - rewrite -std_recursive_mutex.register_thread. - rewrite Hgpool HN. iFrame "#∗". - Qed. - - Lemma account_unregister_thread (this : ptr) gpool N q th : - this |-> account_acquireableR gpool N q th |-- - this |-> accountR gpool N q ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)). - Proof. - rewrite accountR.unlock account_acquireableR.unlock !_at_exists. - iIntros "(%g & HR)". - iEval (rewrite !_at_sep !_at_pureR !_at_as_Rep) in "HR". - iDestruct "HR" as "(HR & Hacquireable & %Hg)". - iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". - iDestruct "Hacquireable" as "(_ & Htoken & Hname)". - destruct Hg as [Hgpool HN]. - rewrite Hgpool HN. iFrame "Hname". - iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. + iSplit. + - iIntros "(#Hth & (%g & HR) & Hname)". + iEval (rewrite !_at_sep !_at_pureR) in "HR". + iDestruct "HR" as "(HR & Htoken & %Hg)". + destruct Hg as [Hgpool HN]. + iExists g. rewrite !_at_sep !_at_pureR !_at_as_Rep. + iFrame "HR". iSplitL "Htoken Hname"; last done. + rewrite -std_recursive_mutex.register_thread. + rewrite Hgpool HN. iFrame "#∗". + - iIntros "(%g & HR)". + iEval (rewrite !_at_sep !_at_pureR !_at_as_Rep) in "HR". + iDestruct "HR" as "(HR & Hacquireable & %Hg)". + iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". + iDestruct "Hacquireable" as "(#Hth & Htoken & Hname)". + destruct Hg as [Hgpool HN]. + rewrite Hgpool HN. iFrame "Hth Hname". + iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. Qed. #[local] Instance balanceR_learn : LearnEq2 account_balanceR. @@ -486,16 +479,20 @@ Section clients. with "Hnames") as "[Hnames Hto]". iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "from") _) as "Hfrom_account". iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "to") _) as "Hto_account". - iDestruct (account_register_thread with "[$Hfrom_account $Hfrom]") as "Hfrom_account"; first go. - iDestruct (account_register_thread with "[$Hto_account $Hto]") as "Hto_account"; first go. + iDestruct (bi.equiv_entails_1_1 _ _ (account_register_thread _ _ _ _ _) + with "[$Hfrom_account $Hfrom]") as "Hfrom_account"; first go. + iDestruct (bi.equiv_entails_1_1 _ _ (account_register_thread _ _ _ _ _) + with "[$Hto_account $Hto]") as "Hto_account"; first go. iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". go. iExists (1$m)%cQp, (1$m)%cQp. go. iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "from") _ th) as "Hfrom_account". iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "to") _ th) as "Hto_account". - iDestruct (account_unregister_thread with "Hfrom_account") as "[Hfrom_account Hfrom]". - iDestruct (account_unregister_thread with "Hto_account") as "[Hto_account Hto]". + iEval (rewrite -account_register_thread) in "Hfrom_account". + iEval (rewrite -account_register_thread) in "Hto_account". + iDestruct "Hfrom_account" as "(_ & Hfrom_account & Hfrom)". + iDestruct "Hto_account" as "(_ & Hto_account & Hto)". iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". go. iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th From cd3521e62fa24016ae17be27f1ca60b2de08c300 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 03:59:13 +0000 Subject: [PATCH 049/109] update comments in transfer_with_mutex_cpp_proof.v --- .../test/mutex/transfer_with_mutex_cpp_proof.v | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index 08f82d9b..b4171250 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -66,7 +66,7 @@ Definition account_balanceR `{Σ : cpp_logic, σ : genv} (n : Z) : Rep := _field "C::balance" |-> ulongR 1$m n. #[only(lazy_unfold(export),timeless)] derive account_balanceR. -(* knowledge of [this] and [this->mutex] *) +(* ownership of [this->mutex] and balanceR proctected by the mutex *) sl.lock Definition account_mutexR `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} @@ -78,7 +78,7 @@ Definition account_mutexR @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)). #[only(cfractional,ascfractional,type_ptr,lazy_unfold(export))] derive account_mutexR. -(** account_mutexR and balanceR protected by the mutex *) +(** The account rep. Everything that the constructor of [C] returns. *) sl.lock Definition accountR `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} @@ -89,7 +89,7 @@ Definition accountR std_recursive_mutex.rmutex_inv_namespace g = N |]. #[only(type_ptr="C")] derive accountR. -(** A thread's registered account handle, ready for acquisition. *) +(** The user handle of the account. *) sl.lock Definition account_acquireableR `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} From e32b2010e582d3c2a367282307fc3a32a32f5375 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 04:16:54 +0000 Subject: [PATCH 050/109] remove unused current_thread in recursive_mutex.v ctor spec --- rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v | 1 - rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 3749b44b..0121cfbb 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -170,7 +170,6 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \persist{th} current_thread th \pre{pool N TT P xs} |> @tele_app TT (fun _ => mpred) P xs \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) \post Exists g, diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index a768a82b..e5512c0a 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -463,6 +463,8 @@ Section with_cpp. iModIntro; work. iExists pool, N. work. rewrite /acquireable /=. + (* The owner slot is unused while the initial count is zero. *) + set th : thread_idT := inhabitant. iMod (own_alloc (●E (O, th, (1$m)%cQp) ⋅ ◯E (O, th, (1$m)%cQp))) as (g) "(? & ?)". { apply excl_auth_valid. } wname [_ |-> _ _ _] "a". From 341a7acfeabc1bf2d4be586ced6a703ef76d5b45 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 29 Sep 2026 04:18:44 +0000 Subject: [PATCH 051/109] update comment in recursive_mutex.v --- rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 0121cfbb..cb543ac7 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -98,7 +98,7 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Existing Class G. #[global] Arguments G {_ _} Σ : assert. - (** A pool is shared by mutexes; each mutex reserves its own namespace. *) + (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) Parameter pool_name : gname -> iprop.gname. Parameter rmutex_inv_namespace : gname -> namespace. #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. From b2f2382418c00011bb811497bb6322ffb47705f4 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 1 Oct 2026 01:58:22 +0000 Subject: [PATCH 052/109] add C::transfer() as a simpler alternative to transfer(); make inv namespaces more explicit --- .../test/mutex/transfer_with_mutex.cpp | 7 + .../mutex/transfer_with_mutex_cpp_proof.v | 255 +++++++++++------- 2 files changed, 166 insertions(+), 96 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp index 8bf9d084..51b8821e 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex.cpp @@ -28,6 +28,13 @@ class C { std::lock_guard lg(mut); std::printf("Balance: %lu\n", balance); } + + void transfer(C& to, unsigned long i) { + std::lock_guard lg_from(mut); + std::lock_guard lg_to(to.mut); + this->balance -= i; + to.balance += i; + } }; diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index b4171250..58a5129d 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -89,19 +89,12 @@ Definition accountR std_recursive_mutex.rmutex_inv_namespace g = N |]. #[only(type_ptr="C")] derive accountR. -(** The user handle of the account. *) -sl.lock -Definition account_acquireableR - `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} - (gpool : iprop.gname) (N : namespace) (q : cQp.t) (th : thread_idT) : Rep := - Exists g, - account_mutexR g q ** - as_Rep (fun this : ptr => - std_recursive_mutex.acquireable (TT := balance_args) g q th - std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n)) ** - pureR [| std_recursive_mutex.pool_name g = gpool /\ - std_recursive_mutex.rmutex_inv_namespace g = N |]. -#[only(type_ptr="C")] derive account_acquireableR. +(** The two account mutexes use distinct namespaces. *) +Definition from_namespace : namespace := nroot .@@ "transfer" .@ "from". +Definition to_namespace : namespace := nroot .@@ "transfer" .@ "to". + +Lemma transfer_namespaces_disjoint : (↑from_namespace : coPset) ## ↑to_namespace. +Proof. rewrite /from_namespace /to_namespace. solve_ndisj. Qed. (** Names of the two closure types and their instantiated [C::call] methods. *) Definition outer_lambda : name := @@ -118,31 +111,6 @@ Section class_C. Context {HAS_THREADS : HasStdThreads Σ}. Context `{!std_recursive_mutex.G Σ}. - Lemma account_register_thread (this : ptr) gpool N q th : - current_thread th ** this |-> accountR gpool N q ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) ⊣⊢ - this |-> account_acquireableR gpool N q th. - Proof. - rewrite accountR.unlock account_acquireableR.unlock !_at_exists. - iSplit. - - iIntros "(#Hth & (%g & HR) & Hname)". - iEval (rewrite !_at_sep !_at_pureR) in "HR". - iDestruct "HR" as "(HR & Htoken & %Hg)". - destruct Hg as [Hgpool HN]. - iExists g. rewrite !_at_sep !_at_pureR !_at_as_Rep. - iFrame "HR". iSplitL "Htoken Hname"; last done. - rewrite -std_recursive_mutex.register_thread. - rewrite Hgpool HN. iFrame "#∗". - - iIntros "(%g & HR)". - iEval (rewrite !_at_sep !_at_pureR !_at_as_Rep) in "HR". - iDestruct "HR" as "(HR & Hacquireable & %Hg)". - iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". - iDestruct "Hacquireable" as "(#Hth & Htoken & Hname)". - destruct Hg as [Hgpool HN]. - rewrite Hgpool HN. iFrame "Hth Hname". - iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. - Qed. - #[local] Instance balanceR_learn : LearnEq2 account_balanceR. Proof. solve_learnable. Qed. #[local] Instance account_mutexR_learn : @@ -225,7 +193,8 @@ Section class_C. \arg{f} "f" (Vptr f) \persist type_ptr (Tnamed cl) f \persist{th} current_thread th - \prepost{gpool N q} this |-> account_acquireableR gpool N q th + \prepost{gpool N q} this |-> accountR gpool N q ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) \pre{K} ∀ v : Z, this |-> account_balanceR v -* balance_callback cl f (this ,, _field "C::balance") @@ -245,11 +214,16 @@ Section class_C. cbn. iIntros "H". iDestruct "H" as (f th gpool N q K) - "(%Hargs & #Hf & #Hth & HR & Hcallback & Hpost)". - iEval (rewrite account_acquireableR.unlock _at_exists) in "HR". + "(%Hargs & #Hf & #Hth & (HR & Hnames) & Hcallback & Hpost)". + iEval (rewrite accountR.unlock _at_exists) in "HR". iDestruct "HR" as (g) "HR". - iEval (rewrite !_at_sep !_at_pureR _at_as_Rep) in "HR". - iDestruct "HR" as "(HR & Hacquireable & %Hg)". + iEval (rewrite !_at_sep !_at_pureR) in "HR". + iDestruct "HR" as "(HR & Htoken & %Hg)". + iAssert (std_recursive_mutex.acquireable (TT := balance_args) g q th + std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I + with "[Htoken Hnames]" as "Hacquireable". + { rewrite -std_recursive_mutex.register_thread. + destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "#∗". } iExists f, g, q, (std_recursive_mutex.acquireable (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n) ** K)%I. @@ -281,9 +255,12 @@ Section class_C. iFrame. - iIntros "(HR & Hacquireable & HK)". iApply "Hpost". iFrame "HK". - rewrite account_acquireableR.unlock _at_exists. iExists g. - rewrite !_at_sep !_at_pureR _at_as_Rep. - iFrame. done. + iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". + iDestruct "Hacquireable" as "(_ & Htoken & Hnames)". + destruct Hg as [Hgpool HN]. + iEval (rewrite Hgpool HN) in "Hnames". iFrame "Hnames". + rewrite accountR.unlock _at_exists. iExists g. + rewrite !_at_sep !_at_pureR. iFrame. done. Qed. Local Lemma outer_call_refines : outer_call_internal_spec |-- outer_call_spec. @@ -298,6 +275,74 @@ Section class_C. Lemma inner_call_ok : verify?[source] inner_call_spec. Proof. work. wapply inner_call_refines. wapply inner_call_internal_ok. work. Qed. + cpp.spec "C::transfer(C&, unsigned long)" as C_transfer_spec from source with ( + \this from + \arg{to} "to" (Vptr to) + \arg{i} "i" (Vint i) + \persist{th} current_thread th + \prepost{gpool qfrom qto} + from |-> accountR gpool from_namespace qfrom ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑from_namespace)) ** + to |-> accountR gpool to_namespace qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑to_namespace)) + \post emp + ). + + Lemma C_transfer_ok : verify[source] C_transfer_spec. + Proof. + verify_spec; go. + rewrite accountR.unlock. go. + lazymatch goal with + | |- context[transfer_lock_guard.R (this ,, _) ?gfrom _ _] => + rename gfrom into source_g + end. + iDestruct select (MutexSets.my_mutexes _ th + (coPset.CoPset (↑from_namespace))) as "Hfrom_names". + iEval (rewrite -b) in "Hfrom_names". + iDestruct (bi.equiv_entails_1_1 _ _ + (std_recursive_mutex.register_thread source_g qfrom th (TT := balance_args) + (fun n : Z => this |-> account_balanceR n)%I) + with "[$]") as "?"; first go. + iDestruct select (MutexSets.my_mutexes _ th + (coPset.CoPset (↑to_namespace))) as "Hto_names". + iEval (rewrite -a -b0) in "Hto_names". + iDestruct (bi.equiv_entails_1_1 _ _ + (std_recursive_mutex.register_thread g qto th (TT := balance_args) + (fun n : Z => to |-> account_balanceR n)%I) + with "[$]") as "?"; first go. + go. + rewrite std_recursive_mutex.acquire.unlock in H1, H2. + destruct H1 as [[from_balance []] Hfrom]. inversion Hfrom; subst. + destruct H2 as [[to_balance []] Hto]. inversion Hto; subst. + go. + (* Return the updated destination balance when lg_to is destroyed. *) + iExists (cQp.scale (1 / 2) qto), + (std_recursive_mutex.acquireable (TT := balance_args) g qto th + std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I. + rewrite std_recursive_mutex.release.unlock /=. go. + (* Return the updated source balance when lg_from is destroyed. *) + iExists (cQp.scale (1 / 2) qfrom), + (std_recursive_mutex.acquireable (TT := balance_args) source_g qfrom th + std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I. + rewrite std_recursive_mutex.release.unlock /=. go. + rewrite accountR.unlock /std_recursive_mutex.acquireable. go with br_erefl. + rewrite a b b0. go. + Qed. + + (** Link the C implementation and both lock-guard operations. *) + Lemma C_transfer_link : + denoteModule source + ** std_recursive_mutex.std_lock_spec_alt + ** std_recursive_mutex.std_unlock_spec_alt + |-- C_transfer_spec. + Proof. + work. + wapply C_transfer_ok. + wapply transfer_lock_guard.ctor_ok. + wapply transfer_lock_guard.dtor_ok. + work. + Qed. + #[local] Instance balanceR_tele_timeless (this : ptr) args : Timeless (@tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args). Proof. destruct args as [n []]. apply _. Qed. @@ -359,10 +404,6 @@ Section clients. Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_hints.fin) accountR). Proof. solve_learnable. Qed. - #[local] Instance account_acquireableR_learn : - Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_eq ==> learn_hints.fin) account_acquireableR). - Proof. solve_learnable. Qed. - cpp.spec (lambda_call outer_lambda) from source inline. cpp.spec (lambda_call inner_lambda) from source inline. cpp.spec (Nscoped outer_lambda Ndtor) from source inline. @@ -381,15 +422,17 @@ Section clients. Lemma transfer_seq_ok : verify[source] transfer_seq_spec. Proof. verify_spec; go. Qed. - (** Transfer balances using the two accounts' acquisition handles. *) - cpp.spec "transfer(C&, C&, unsigned long)" as transfer_spec from source with ( + (** Transfer balances using account ownership and per-thread namespace resources. *) + cpp.spec "transfer(C&, C&, unsigned long)" as transfer_general_spec from source with ( \arg{from} "from" (Vptr from) \arg{to} "to" (Vptr to) \arg{i} "i" (Vint i) \persist{th} current_thread th \prepost{gpool Nfrom Nto qfrom qto} - from |-> account_acquireableR gpool Nfrom qfrom th ** - to |-> account_acquireableR gpool Nto qto th + from |-> accountR gpool Nfrom qfrom ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nfrom)) ** + to |-> accountR gpool Nto qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)) \post emp). Local Lemma call_with_continuation (A : mpred -> mpred) R Q : @@ -399,16 +442,17 @@ Section clients. iIntros "[HR HK]". iApply "HK". iFrame. Qed. - Lemma transfer_ok : inner_call_spec ** transfer_seq_spec |-- verify[source] transfer_spec. + Lemma transfer_general_ok : inner_call_spec ** transfer_seq_spec |-- verify[source] transfer_general_spec. Proof. verify_shift; go. iExists qfrom. go. iApply call_with_continuation. iIntros (v1) "Hfrom_balance". - (* The outer acquisition exposes from's balance; to retains its handle. *) - wname [account_acquireableR] "Hto". - iAssert (from |-> account_balanceR v1 ** to |-> account_acquireableR gpool Nto qto th)%I - with "[$Hfrom_balance $Hto]" as "Hafter_from". + (* The outer acquisition exposes from's balance; to retains its account and namespace. *) + wname [accountR] "Hto". + iAssert (from |-> account_balanceR v1 ** to |-> accountR gpool Nto qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I + with "[$]" as "Hafter_from". iDestruct "Hafter_from" as "[? ?]". rewrite /balance_callback. iIntros (Qfrom) "HKfrom". go. iExists qto. go. @@ -426,30 +470,66 @@ Section clients. with "[$]" as "Hafter_transfer". iDestruct "Hafter_transfer" as "[? ?]". iApply "HKto". go. - (* Returning from to.call restores its fractional account and acquisition handle. *) + (* Returning from to.call restores its account and namespace resource. *) iAssert (from ,, _field "C::balance" |-> ulongR 1$m (trim 64 (v1 - i)) ** - to |-> account_acquireableR gpool Nto qto th)%I + to |-> accountR gpool Nto qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I with "[$]" as "Hafter_to". - iDestruct "Hafter_to" as "[Hfrom_balance Hto]". + iDestruct "Hafter_to" as "(Hfrom_balance & Hto & Hto_names)". iApply "HKfrom". iExists (trim 64 (v1 - i)). iSplitL "Hfrom_balance". { iDestruct "Hfrom_balance" as "?". go. } - iIntros "Hfrom". - (* Returning from from.call restores both fractional accounts. *) - iAssert (from |-> account_acquireableR gpool Nfrom qfrom th ** to |-> account_acquireableR gpool Nto qto th)%I - with "[$Hfrom $Hto]" as "Hafter_from". + iIntros "(Hfrom & Hfrom_names)". + (* Returning from from.call restores both accounts and namespace resources. *) + iAssert (from |-> accountR gpool Nfrom qfrom ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nfrom)) ** + to |-> accountR gpool Nto qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I + with "[$Hfrom $Hfrom_names $Hto $Hto_names]" as "Hafter_from". iDestruct "Hafter_from" as "[? ?]". go. wname [bi_wand] "Hpost". iSpecialize ("Hpost" with "[$]"). iModIntro. iNext. iApply "Hpost". go. Qed. - Lemma transfer_link : - denoteModule source ** outer_call_spec ** inner_call_spec |-- transfer_spec. + Lemma transfer_general_link : + denoteModule source ** outer_call_spec ** inner_call_spec |-- transfer_general_spec. Proof. - work. wapply transfer_ok. wapply transfer_seq_ok. work. + work. wapply transfer_general_ok. wapply transfer_seq_ok. work. Qed. + (** Choose both mutex namespaces for the callback-based transfer. *) + Definition transfer_body : WpSpec mpred val val := + ( + \arg{from} "from" (Vptr from) + \arg{to} "to" (Vptr to) + \arg{i} "i" (Vint i) + \persist{th} current_thread th + \prepost{gpool qfrom qto} + from |-> accountR gpool from_namespace qfrom ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑from_namespace)) ** + to |-> accountR gpool to_namespace qto ** + MutexSets.my_mutexes gpool th (coPset.CoPset (↑to_namespace)) + \post emp + ). + + cpp.spec "transfer(C&, C&, unsigned long)" as transfer_spec from source with + (\exact Reduce transfer_body). + + Lemma transfer_specialize : transfer_general_spec |-- transfer_spec. + Proof. + apply specify_mono. intros args post. cbn. + work. + iExists qfrom, qto. work. + Qed. + + Lemma transfer_ok : inner_call_spec ** transfer_seq_spec |-- verify[source] transfer_spec. + Proof. work. wapply transfer_specialize. wapply transfer_general_ok. work. Qed. + + Lemma transfer_link : + denoteModule source ** outer_call_spec ** inner_call_spec |-- transfer_spec. + Proof. work. wapply transfer_specialize. wapply transfer_general_link. work. Qed. + cpp.spec "main()" as main_spec from source with ( \persist{th} current_thread th \post[Vint 0] emp @@ -466,41 +546,24 @@ Section clients. with "Hmap") as "[Hmap Hnames]". iEval (rewrite left_id_L) in "Hmap". go. - iExists gpool, (nroot .@@ "transfer" .@ "from"); go. - iExists gpool, (nroot .@@ "transfer" .@ "to"); go. - have Hdisjoint : (↑(nroot .@@ "transfer" .@ "from") : coPset) ## - ↑(nroot .@@ "transfer" .@ "to") by solve_ndisj. + iExists gpool, from_namespace; go. + iExists gpool, to_namespace; go. + have Hdisjoint := transfer_namespaces_disjoint. iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th ⊤ - (↑(nroot .@@ "transfer" .@ "from")) ltac:(set_solver) + (↑from_namespace) ltac:(set_solver) with "Hnames") as "[Hnames Hfrom]". iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th - (⊤ ∖ ↑(nroot .@@ "transfer" .@ "from")) - (↑(nroot .@@ "transfer" .@ "to")) ltac:(set_solver) + (⊤ ∖ ↑from_namespace) + (↑to_namespace) ltac:(set_solver) with "Hnames") as "[Hnames Hto]". - iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "from") _) as "Hfrom_account". - iDestruct select (_ |-> accountR gpool (nroot .@@ "transfer" .@ "to") _) as "Hto_account". - iDestruct (bi.equiv_entails_1_1 _ _ (account_register_thread _ _ _ _ _) - with "[$Hfrom_account $Hfrom]") as "Hfrom_account"; first go. - iDestruct (bi.equiv_entails_1_1 _ _ (account_register_thread _ _ _ _ _) - with "[$Hto_account $Hto]") as "Hto_account"; first go. - iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". + iDestruct "Hfrom" as "?". iDestruct "Hto" as "?". go. iExists (1$m)%cQp, (1$m)%cQp. go. - iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "from") _ th) - as "Hfrom_account". - iDestruct select (_ |-> account_acquireableR gpool (nroot .@@ "transfer" .@ "to") _ th) - as "Hto_account". - iEval (rewrite -account_register_thread) in "Hfrom_account". - iEval (rewrite -account_register_thread) in "Hto_account". - iDestruct "Hfrom_account" as "(_ & Hfrom_account & Hfrom)". - iDestruct "Hto_account" as "(_ & Hto_account & Hto)". - iDestruct "Hfrom_account" as "?". iDestruct "Hto_account" as "?". - go. iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th - (⊤ ∖ ↑(nroot .@@ "transfer" .@ "from")) - (↑(nroot .@@ "transfer" .@ "to")) ltac:(set_solver) + (⊤ ∖ ↑from_namespace) + (↑to_namespace) ltac:(set_solver) with "[$]") as "Hnames". iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th ⊤ - (↑(nroot .@@ "transfer" .@ "from")) ltac:(set_solver) + (↑from_namespace) ltac:(set_solver) with "[$]") as "Hnames". iDestruct (MutexSets.mutex_sets_free_thread gpool {[th]} th ltac:(set_solver) with "[$]") as "Hmap". From 6fee5a5e7c24bf4661dbaafd6674f80616746e66 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 1 Oct 2026 02:28:39 +0000 Subject: [PATCH 053/109] add comment --- .../test/mutex/transfer_with_mutex_cpp_proof.v | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index 58a5129d..449dafd9 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -78,7 +78,9 @@ Definition account_mutexR @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)). #[only(cfractional,ascfractional,type_ptr,lazy_unfold(export))] derive account_mutexR. -(** The account rep. Everything that the constructor of [C] returns. *) +(** The account rep. Everything that the constructor of [C] returns. + The recursive mutex ctor can allocate a [cinv] assocaited with any given + [N : namespace], and returns (most of) account_mutexR. *) sl.lock Definition accountR `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} From ea252701b865814faa3cda50fa470a0af8946183 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 1 Oct 2026 05:34:10 +0000 Subject: [PATCH 054/109] add a comment --- rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index cb543ac7..15c4993e 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -154,6 +154,8 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). (** Owning any given_token (which implies [th] holds the lock) contradicts resources needed to destruct the lock. *) + (* FIXME should the following be [acquireable Held] instead of [given_token] + and [given_token] be removed from this module? *) Parameter locked_contradict_full_token : forall (this : ptr) g q qt th n P, this |-> R g q P ** token g 1$m ** given_token g qt th n |-- (|={⊤}=> False). From 8675f1882a5dc351b35f9609a8721914b5e872d1 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 1 Oct 2026 05:35:06 +0000 Subject: [PATCH 055/109] [custom_recursive_mutex.hpp] use hash for thread id equality checking --- rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp index cbaf109e..e44ecade 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp @@ -3,10 +3,11 @@ #include #include +size_t id_hash = std::hash{}(std::thread::id{}); #pragma once class MyRecursiveMutex { // not protected by the lock, but atomic - std::atomic m_owner{}; + std::atomic m_owner{id_hash}; // protected by the lock unsigned long long m_count{0}; @@ -18,7 +19,7 @@ class MyRecursiveMutex { // TODO: add the token/given_token accounting, maybe with q*tickets void lock() { - std::thread::id this_id{std::this_thread::get_id()}; + size_t this_id = std::hash{}(std::this_thread::get_id()); // Open cinv if (m_owner != this_id) { // here, m_owner is: @@ -59,7 +60,7 @@ class MyRecursiveMutex { m_count--; if (m_count == 0) { m_lock.unlock(); - m_owner = std::thread::id{}; + m_owner = id_hash; } } }; From a08d8ad4383d4336abc06554a37bd5bbab1fce06 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 1 Oct 2026 05:42:12 +0000 Subject: [PATCH 056/109] [custom_recursive_mutex.hpp] unset owner id before releasing the lock --- rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp index e44ecade..0659d92f 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp @@ -59,8 +59,8 @@ class MyRecursiveMutex { void unlock() { m_count--; if (m_count == 0) { - m_lock.unlock(); m_owner = id_hash; + m_lock.unlock(); } } }; From 87b9ea10b765ff899b43aa4f53b71fe7c83c745b Mon Sep 17 00:00:00 2001 From: William Mansky Date: Mon, 31 Aug 2026 15:25:35 -0500 Subject: [PATCH 057/109] started on implementation section --- papers/cpp-mutex/specs.tex | 98 ++++++++++++++++++++++++++++++++----- papers/cpp-mutex/stdcpp.tex | 2 + 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 6a540fde..61c5c38a 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -29,6 +29,7 @@ \section{Three Flavors of Mutex} \newcommand{\smutex}[2]{\ensuremath{\mathsf{smutex}_{#1}(#2)}} \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} +\label{mutex-spec} \begin{figure}[htb] \begin{lstlisting} @@ -186,7 +187,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \end{figure} \subsection{\texorpdfstring{\lstinline{std::recursive_mutex}}{std::recursive\_mutex}} -\emph{Recursive} or \emph{reentrant} mutexes can be acquired multiple times by the same thread, and only become available to another thread once every acquire has been matched with a release. Recursive mutexes are popular in object-oriented programming, allowing a method to acquire a lock every time it is called even if it is called from another locked method. A canonical recursive mutex implementation is shown in Figure~\ref{rmutex-simple}. When no thread holds the lock, \lstinline{count} is 0 and \lstinline{owner} is \lstinline{null}. When a thread that does not already own the lock tries to acquire it, it calls the \lstinline{lock} method of an underlying conventional mutex; likewise, when the owner releases the lock for the last time (\lstinline{count} is 1), it \lstinline{unlock}s the underlying mutex. However, when the owner re-acquires a lock it already owns, it performs no synchronization operations, instead simply incrementing the count---and likewise, a non-final release simply decrements the count. This avoids redundant synchronization and deadlock while still providing mutual exclusion. %note that an implementation requires a get-thread-id function +\emph{Recursive} or \emph{reentrant} mutexes can be acquired multiple times by the same thread, and only become available to another thread once every acquire has been matched with a release. Recursive mutexes are popular in object-oriented programming, allowing a method to acquire a lock every time it is called even if it is called from another locked method. A canonical recursive mutex implementation is shown in Figure~\ref{rmutex-simple}. When no thread holds the lock, \lstinline{count} is 0 and \lstinline{owner} is \lstinline{null}. When a thread that does not already own the lock tries to acquire it, it calls the \lstinline{lock} method of an underlying conventional mutex; likewise, when the owner releases the lock for the last time (\lstinline{count} is 1), it \lstinline{unlock}s the underlying mutex. However, when the owner re-acquires a lock it already owns, it performs no synchronization operations, instead simply incrementing the count---and likewise, a non-final release simply decrements the count. This avoids redundant synchronization and deadlock while still providing mutual exclusion. %note that an implementation requires a get-thread-id function %note that there's no natural daring version of the recursive mutex---only the thread ID distinguishes re-acquisition from mutex-breaking \begin{figure}[htb] \begin{lstlisting} @@ -331,6 +332,7 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} C++'s \lstinline{shared_mutex} class is more commonly known as a \emph{reader-writer lock}. It provides two separate pairs of acquire and release operations, \emph{exclusive} \lstinline{lock}/\lstinline{unlock} and \emph{shared} \lstinline{lock_shared}/\lstinline{unlock_shared}; while \lstinline{lock} and \lstinline{unlock} ensure mutual exclusion, any number of threads can successfully perform \lstinline{lock_shared} at once as long as no thread has acquired the lock exclusively. Reader-writer locks have been modeled in separation logic by \citet{heaplang-rw-lock}, and we build on their work, but must also account for C++'s additional restrictions: no thread may acquire shared ownership of the mutex more than once, and only threads that have acquired it may release it. +\ignore{ \begin{figure}[htb] \begin{lstlisting} %not sure whether this is the right canonical implementation @@ -358,6 +360,7 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} \caption{A simple implementation of \lstinline{std::shared_mutex}} \label{smutex-simple} \end{figure} +} Following \citet{heaplang-rw-lock}, we take the resource $P$ protected by a shared mutex to be a fractional predicate; calling \lstinline{lock} yields full ownership of the resource $P(1)$, @@ -386,31 +389,42 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} \] \begin{figure}[htb] -\begin{lstlisting} +\begin{lstlisting}[escapeinside = {(*@}{@*)}] bool resource = false; std::shared_mutex m; +(*@ $\{ \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) void set_resource() { + (*@ $\{ \currentthread{t} \ast \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) m.lock(); + (*@ $\{ ... \ast \texttt{resource} \mapsto b \ast \smlocked{\gamma}{t} \}$ @*) resource = true; + (*@ $\{ ... \ast \texttt{resource} \mapsto \mathsf{true} \ast \smlocked{\gamma}{t} \}$ @*) m.unlock(); + (*@ $\{ ... \ast \notlocked{\gamma}{t} \}$ @*) } +(*@ $\{ \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) +(*@ $\{ \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) void await_resource() { + (*@ $\{ \currentthread{t} \ast \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) for (;;) { m.lock_shared(); + (*@ $\{ ... \ast \texttt{resource} \mapsto_{q'} b \ast \smreaderlocked{\gamma}{t}{q'} \}$ @*) bool ret = resource; m.unlock_shared(); + (*@ $\{ ... \ast \notlocked{\gamma}{t} \}$ @*) if (ret) break; } } +(*@ $\{ \texttt{m} \mapsto_q \smutex{\gamma}{P} \ast \notlocked{\gamma}{t} \}$ @*) \end{lstlisting} -\caption{A program using \lstinline{shared_mutex}} -\label{shared-mutex-ex} +\caption{A program using \lstinline{shared_mutex} and its proof sketch} +\label{shared-mutex-ex-proof} \end{figure} -Figure~\ref{shared-mutex-ex} shows an example using \lstinline{shared_mutex}. Exclusive ownership of \lstinline{m} gives write access to \lstinline{resource}, while shared ownership gives read access to \lstinline{resource}. %proof sketch +Figure~\ref{shared-mutex-ex-proof} shows an example using \lstinline{shared_mutex}. Exclusive ownership of \lstinline{m} gives write access to \lstinline{resource}, while shared ownership gives read access to \lstinline{resource}. Its proof uses the resource predicate $P(q) \triangleq \exists b.\ \texttt{resource} \mapsto_q b$, so that \lstinline{lock} gives full ownership of \lstinline{resource} while \lstinline{lock_shared} gives partial (read-only) ownership. \ignore{ \begin{mathpar} @@ -490,21 +504,25 @@ \subsection{The User Library} TODO cite the Iris reader/writer lock library TODO mutex is not implemented with the user library yet +All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. + The $\mathsf{not\_locked}$ ghost state provides an interface that allows a thread to acquire locked resources at most once regardless of the mutex kinds. Each flavor of mutex is associated with a disjoint set of thread IDs, managed by one authoritative piece \allusers\ and fragment pieces \notlocked{\gamma}{t}: \begin{align*} - \allusers^{\gamma}(T) - &\eqdef \mathsf{own}^{\gamma}(\mathord{\bullet}T), \\ + \usedthreads{\gamma}{T} + &\eqdef \ownGhost{\gamma}{\mathord{\bullet}T} \\ % \mathsf{users}^{\gamma}(T) % &\eqdef \mathsf{own}^{\gamma}(\mathord{\circ}T), \\ - \mathsf{not\_locked}^{\gamma}(t) - &\eqdef \mathsf{own}^{\gamma}(\mathord{\circ}\{t\}). + \notlocked{\gamma}{t} + &\eqdef \ownGhost{\gamma}{\mathord{\circ}\{t\}} \end{align*} The \allusers{T} ghost state tracks all "logged-in" threads $T$. Each thread with thread ID $t$ can get a \notlocked{\gamma}{t} handle for that mutex with the \texttt{login} rule, and the handle is -unique for each thread (not-locked-unique). The mutex invariants are slightly +unique for each thread (not-locked-unique). %write out all these rules! + +The mutex invariants are slightly different for different kinds of mutexes, but they all ensure that a thread must trade in its unique \notlocked{\gamma}{t} handle to acquire protected resources. This ensures that a thread can't acquire protected resources if it already has @@ -521,5 +539,63 @@ \subsection{The User Library} (\texttt{all-not\_locked-logged-out}), and so the mutex is not held by any thread, thus safe to deallocate. +%specific locked instances here or below? + \subsection{Mutex Implementations} -%move sample implementations here? +\subsubsection{\lstinline{std::mutex}} + +\newcommand{\token}[1]{\ensuremath{\ownGhost{#1}{()}}} + +\begin{figure}[htb] +\begin{lstlisting} +class Mutex1 { + std::atomic lock; + + void lock(){ + while(lock.exchange(true)) { yield(); } + } + + void unlock(){ + lock = false; + } +} +\end{lstlisting} +\caption{A simple spinlock implementation of \lstinline{std::mutex}} +\label{mutex-impl1} +\end{figure} + +\begin{figure}[htb] +\begin{lstlisting} +class Mutex2 { + std::atomic lock; + std::thread::id owner; + + void lock(){ + while(lock.exchange(true)) { yield(); } + owner = get_id(); + } + + void unlock(){ + assert(owner == get_id()); + owner = std::thread::id(); + lock = false; + } + + ~Mutex2() { + assert(!lock); + assert(owner == std::thread::id()); //unowned + } +} +\end{lstlisting} +\caption{An implementation of \lstinline{std::mutex} that checks some thread requirements} +\label{mutex-impl2} +\end{figure} + +We consider two spinlock implementations of \lstinline{std::mutex}. The first, in Figure~\ref{mutex-impl1}, is a straightforward implementation similar to that used in HeapLang: \lstinline{lock} atomically exchanges \lstinline{true} with the current value of \lstinline{lock}, which is \lstinline{true} if the mutex is currently held (in which case the exchange does nothing and the loop continues) and \lstinline{false} if the mutex is not held (in which case the exchange sets \lstinline{lock} to \lstinline{true} and the loop ends). The second, in Figure~\ref{mutex-impl2}, additionally records the thread ID of the current owner, and uses it to assert some of the mutex requirements: a thread calling \lstinline{unlock} must be the current owner, and when the mutex is destroyed it must not be held by any thread. Both of these implementations meet the informal specification of \lstinline{mutex}: since violating the mutex requirements is undefined behavior, assert failure and abnormal termination is a permissible result. However, the second implementation does \emph{not} meet the usual separation logic specification of locks, in which any thread that holds a transferable $\mathsf{locked}$ predicate can release the lock. We prove that both of these implementations satisfy the mutex specifications of Section~\ref{mutex-spec}. We outline the proof for \lstinline{Mutex2}; the proof for \lstinline{Mutex1} is strictly simpler. The standard Iris approach to verifying a spinlock is to define an invariant of the form $\knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P)}$, where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant +\[I(P) \triangleq \knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast P \ast \texttt{owner} \mapsto \bot)}\] %don't reuse \gamma, or explain that it's a record +Now we can define $\locked{\gamma}{t}$ as $\notlocked{\gamma}{t} \ast \texttt{owner} \mapsto t$. We include $\mathsf{not\_locked}$ in $\mathsf{m\_locked}$ because this immediately gives us $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$, and the exclusivity of $\mapsto$ guarantees that $\notlocked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False}$. Furthermore, $\currentthread{t} \ast \locked{\gamma}{t}$ guarantees that \lstinline{owner} is precisely the current thread $t$, so the precondition of \lstinline{unlock} is sufficient to ensure that the \lstinline{assert} will succeed. %But this doesn't seem to be enough to pass !lock in the destructor -- we need something in the true case that's inconsistent with used_threads(\empty). Maybe use some extra ghost state to split not_locked and put half into the invariant. +The representation predicate is defined as $\mutex{\gamma}{P}_q \triangleq I(P) \ast \mathsf{tok}_q$. With these definitions, we can prove that each method meets its specification: ... + +\subsubsection{\lstinline{std::recursive_mutex}} + +\subsubsection{\lstinline{std::shared_mutex}} diff --git a/papers/cpp-mutex/stdcpp.tex b/papers/cpp-mutex/stdcpp.tex index 49d9f262..b522c68d 100644 --- a/papers/cpp-mutex/stdcpp.tex +++ b/papers/cpp-mutex/stdcpp.tex @@ -458,6 +458,8 @@ \section{BRiCk Formalization} \section{Conclusion and Future Work} ... +%the client problem: all preconditions need not_locked, can't be transferred between threads, so if we include it in an abstract predicate that predicate can't be transferred either (even if it uses locks in a way that would never allow double acquire) + condition variables, futures, shared pointers, ... verified Pthread? From 42e29c51a1f40b51ff0010633354b50665d25b11 Mon Sep 17 00:00:00 2001 From: William Mansky Date: Mon, 31 Aug 2026 15:57:43 -0500 Subject: [PATCH 058/109] start on recursive mutex implementation --- papers/cpp-mutex/specs.tex | 69 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 61c5c38a..81400941 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -191,7 +191,7 @@ \subsection{\texorpdfstring{\lstinline{std::recursive_mutex}}{std::recursive\_mu \begin{figure}[htb] \begin{lstlisting} -std::atomic owner; // default val neq any thread ID +std::atomic owner; int count; std::mutex m; @@ -591,11 +591,76 @@ \subsubsection{\lstinline{std::mutex}} \label{mutex-impl2} \end{figure} -We consider two spinlock implementations of \lstinline{std::mutex}. The first, in Figure~\ref{mutex-impl1}, is a straightforward implementation similar to that used in HeapLang: \lstinline{lock} atomically exchanges \lstinline{true} with the current value of \lstinline{lock}, which is \lstinline{true} if the mutex is currently held (in which case the exchange does nothing and the loop continues) and \lstinline{false} if the mutex is not held (in which case the exchange sets \lstinline{lock} to \lstinline{true} and the loop ends). The second, in Figure~\ref{mutex-impl2}, additionally records the thread ID of the current owner, and uses it to assert some of the mutex requirements: a thread calling \lstinline{unlock} must be the current owner, and when the mutex is destroyed it must not be held by any thread. Both of these implementations meet the informal specification of \lstinline{mutex}: since violating the mutex requirements is undefined behavior, assert failure and abnormal termination is a permissible result. However, the second implementation does \emph{not} meet the usual separation logic specification of locks, in which any thread that holds a transferable $\mathsf{locked}$ predicate can release the lock. We prove that both of these implementations satisfy the mutex specifications of Section~\ref{mutex-spec}. We outline the proof for \lstinline{Mutex2}; the proof for \lstinline{Mutex1} is strictly simpler. The standard Iris approach to verifying a spinlock is to define an invariant of the form $\knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P)}$, where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant +We consider two spinlock implementations of \lstinline{std::mutex}. The first, in Figure~\ref{mutex-impl1}, is a straightforward implementation similar to that used in HeapLang: \lstinline{lock} atomically exchanges \lstinline{true} with the current value of \lstinline{lock}, which is \lstinline{true} if the mutex is currently held (in which case the exchange does nothing and the loop continues) and \lstinline{false} if the mutex is not held (in which case the exchange sets \lstinline{lock} to \lstinline{true} and the loop ends). Unlike in HeapLang, a location that will be accessed concurrently by multiple threads must explicitly be declared to be of an \lstinline{atomic} type: data races on non-atomic locations are undefined behavior in C++. The second implementation, in Figure~\ref{mutex-impl2}, additionally records the thread ID of the current owner, and uses it to assert some of the mutex requirements: a thread calling \lstinline{unlock} must be the current owner, and when the mutex is destroyed it must not be held by any thread. (Because \lstinline{owner} is only accessed by the thread that holds the mutex and in the destructor, it does not need to be atomic.) Both of these implementations meet the informal specification of \lstinline{mutex}: since violating the mutex requirements is undefined behavior, assert failure and abnormal termination is a permissible result. However, the second implementation does \emph{not} meet the usual separation logic specification of locks, in which any thread that holds a transferable $\mathsf{locked}$ predicate can release the lock. We prove that both of these implementations satisfy the mutex specifications of Section~\ref{mutex-spec}. We outline the proof for \lstinline{Mutex2}; the proof for \lstinline{Mutex1} is strictly simpler. + +The standard Iris approach to verifying a spinlock is to define an invariant of the form $\knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P)}$, where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant \[I(P) \triangleq \knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast P \ast \texttt{owner} \mapsto \bot)}\] %don't reuse \gamma, or explain that it's a record Now we can define $\locked{\gamma}{t}$ as $\notlocked{\gamma}{t} \ast \texttt{owner} \mapsto t$. We include $\mathsf{not\_locked}$ in $\mathsf{m\_locked}$ because this immediately gives us $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$, and the exclusivity of $\mapsto$ guarantees that $\notlocked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False}$. Furthermore, $\currentthread{t} \ast \locked{\gamma}{t}$ guarantees that \lstinline{owner} is precisely the current thread $t$, so the precondition of \lstinline{unlock} is sufficient to ensure that the \lstinline{assert} will succeed. %But this doesn't seem to be enough to pass !lock in the destructor -- we need something in the true case that's inconsistent with used_threads(\empty). Maybe use some extra ghost state to split not_locked and put half into the invariant. The representation predicate is defined as $\mutex{\gamma}{P}_q \triangleq I(P) \ast \mathsf{tok}_q$. With these definitions, we can prove that each method meets its specification: ... \subsubsection{\lstinline{std::recursive_mutex}} +\begin{figure}[htb] +\begin{lstlisting} +class RecursiveMutex { + std::atomic owner; + int count; + std::mutex m; + + void lock(){ + if(owner != get_id()){ + m.lock(); + owner = get_id(); + } + count++; + } + + void unlock(){ + count--; + if(count == 0){ + m.unlock(); + owner = std::thread::id{}; // reset to default + } + } +} +\end{lstlisting} %destructor? +\caption{A simple implementation of \lstinline{std::recursive_mutex}} +\label{rmutex-simple} +\end{figure} + +The simplest way to implement a recursive mutex is by combining an underlying non-recursive mutex \lstinline{m} with an integer \lstinline{count}: each thread's first acquire and last release of the recursive mutex (per critical section) are performed on \lstinline{m}, while all intervening operations by the same thread only increment and decrement \lstinline{count}. Threads must check whether they already hold the recursive mutex before locking \lstinline{m}, so the \lstinline{owner} field may be accessed concurrently and must be atomic. The abstract state of the lock is a pair $(t, n)$ of optional owning thread and acquisition count, where $t = \bot \leftrightarrow n = 0$. %skipping atomic specs for now +We store this abstract state in a piece of authoritative ghost state, where the invariant $I$ always owns the authoritative copy and the owning thread (if any) owns the fragment. This lets us define the central $\mathsf{holds}$ predicate as: +\begin{align*} +\acquirable{\gamma}{t}{n}{P}{a} \triangleq \currentthread{t}\ \ast\ &\text{if } n = 0 \text{ then } \notlocked{\gamma}{t} \\&\text{ else } \ownGhost{}{\authfrag (t, n)}%acquirable includes current_thread, unlike not_locked and locked: is this what we want? +\end{align*} +%invariant + +%proof sketch \subsubsection{\lstinline{std::shared_mutex}} +\begin{figure}[htb] +\begin{lstlisting} + +std::atomic sharers{0}; + +void lock(){ + int idle = 0; + while(!sharers.compare_exchange_strong(idle, -1)) { idle = 0; } +} + +void unlock(){ + sharers = 0; +} + +void lock_shared(){ + int val; + do { val = sharers; } + while(val < 0 || !sharers.compare_exchange_strong(val, val + 1)) {} +} + +void unlock_shared(){ + sharers--; +} +\end{lstlisting} +\caption{A simple implementation of \lstinline{std::shared_mutex}} +\label{smutex-simple} +\end{figure} From 8afa3b1eeaddf795663e06226daa583dc4914b4f Mon Sep 17 00:00:00 2001 From: Ke Du Date: Thu, 3 Sep 2026 00:58:11 -0500 Subject: [PATCH 059/109] simplify figure 7 --- papers/cpp-mutex/specs.tex | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 81400941..d1a686b3 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -295,10 +295,10 @@ \subsection{\texorpdfstring{\lstinline{std::recursive_mutex}}{std::recursive\_mu (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n}{P}{(a,b)} \}$ @*) void update_a(long x) { m.lock(); - (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n+1}{P}{(a,b)} \} \Rightarrow$ @*) - (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast P(a, b) \ast (\forall a', b'.\ P(a', b') \wand \acquirable{\gamma}{t}{n+1}{P}{(a',b')}) \}$ @*) + (*@ $\{ ... \ast \acquirable{\gamma}{t}{n+1}{P}{(a,b)} \} \Rightarrow$ @*) + (*@ $\{ ... \ast P(a, b) \ast (\forall a', b'.\ P(a', b') \wand \acquirable{\gamma}{t}{n+1}{P}{(a',b')}) \}$ @*) balance_a += x; - (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n+1}{P}{(a+x,b)}) \}$ @*) + (*@ $\{ ... \ast \acquirable{\gamma}{t}{n+1}{P}{(a+x,b)}) \}$ @*) m.unlock(); } (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n}{P}{(a + \texttt{x},b)} \}$ @*) @@ -314,15 +314,16 @@ \subsection{\texorpdfstring{\lstinline{std::recursive_mutex}}{std::recursive\_mu (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n}{P}{(a,b)} \}$ @*) void transfer(int x) { m.lock(); - (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast P(a, b) \ast (\forall a', b'.\ P(a', b') \wand \acquirable{\gamma}{t}{n+1}{P}{(a,b)}) \}$ @*) + (*@ $\{ ... \ast \acquirable{\gamma}{t}{n+1}{P}{(a,b)} \} \provesIff$ @*) + (*@ $\{ ... \ast P(a, b) \ast (\forall a', b'.\ P(a', b') \wand \acquirable{\gamma}{t}{n+1}{P}{(a',b')}) \}$ @*) update_a(x); update_b(-(long)x); - (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n+1}{P}{(a+x,b-x)} \}$ @*) + (*@ $\{ ... \ast \acquirable{\gamma}{t}{n+1}{P}{(a+x,b-x)} \}$ @*) m.unlock(); } (*@ $\{ \texttt{m} \mapsto_q \rmutex{\gamma}{P} \ast \acquirable{\gamma}{t}{n}{P}{(a+x,b-x)} \}$ @*) \end{lstlisting} -\caption{The proof outline for the \lstinline{recursive_mutex} example} +\caption{The proof outline for the \lstinline{recursive_mutex} example. The mutex points-to assertion is unchanged in these functions and abbreviated with ellipses.} \label{rmutex-ex-proof} \end{figure} From 615b5efbf9ebe3b674dabb3c4d09cd2382a907c2 Mon Sep 17 00:00:00 2001 From: Ke Du Date: Thu, 3 Sep 2026 01:00:06 -0500 Subject: [PATCH 060/109] make invariant fit in column --- papers/cpp-mutex/specs.tex | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index d1a686b3..0351fb4f 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -594,8 +594,21 @@ \subsubsection{\lstinline{std::mutex}} We consider two spinlock implementations of \lstinline{std::mutex}. The first, in Figure~\ref{mutex-impl1}, is a straightforward implementation similar to that used in HeapLang: \lstinline{lock} atomically exchanges \lstinline{true} with the current value of \lstinline{lock}, which is \lstinline{true} if the mutex is currently held (in which case the exchange does nothing and the loop continues) and \lstinline{false} if the mutex is not held (in which case the exchange sets \lstinline{lock} to \lstinline{true} and the loop ends). Unlike in HeapLang, a location that will be accessed concurrently by multiple threads must explicitly be declared to be of an \lstinline{atomic} type: data races on non-atomic locations are undefined behavior in C++. The second implementation, in Figure~\ref{mutex-impl2}, additionally records the thread ID of the current owner, and uses it to assert some of the mutex requirements: a thread calling \lstinline{unlock} must be the current owner, and when the mutex is destroyed it must not be held by any thread. (Because \lstinline{owner} is only accessed by the thread that holds the mutex and in the destructor, it does not need to be atomic.) Both of these implementations meet the informal specification of \lstinline{mutex}: since violating the mutex requirements is undefined behavior, assert failure and abnormal termination is a permissible result. However, the second implementation does \emph{not} meet the usual separation logic specification of locks, in which any thread that holds a transferable $\mathsf{locked}$ predicate can release the lock. We prove that both of these implementations satisfy the mutex specifications of Section~\ref{mutex-spec}. We outline the proof for \lstinline{Mutex2}; the proof for \lstinline{Mutex1} is strictly simpler. -The standard Iris approach to verifying a spinlock is to define an invariant of the form $\knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P)}$, where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant -\[I(P) \triangleq \knowInv{}{(\texttt{lock} \mapsto \mathsf{true}) \vee (\texttt{lock} \mapsto \mathsf{false} \ast P \ast \texttt{owner} \mapsto \bot)}\] %don't reuse \gamma, or explain that it's a record +The standard Iris approach to verifying a spinlock is to define an invariant of the form +\[ + \knowInv{}{\begin{gathered} + (\texttt{lock} \mapsto \mathsf{true}) \\ + {}\vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P) + \end{gathered}}, +\] +where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant +\[ + I(P) \triangleq \knowInv{}{\begin{gathered} + (\texttt{lock} \mapsto \mathsf{true}) \\ + {}\vee (\texttt{lock} \mapsto \mathsf{false} \ast P + \ast \texttt{owner} \mapsto \bot) + \end{gathered}} +\] %don't reuse \gamma, or explain that it's a record Now we can define $\locked{\gamma}{t}$ as $\notlocked{\gamma}{t} \ast \texttt{owner} \mapsto t$. We include $\mathsf{not\_locked}$ in $\mathsf{m\_locked}$ because this immediately gives us $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$, and the exclusivity of $\mapsto$ guarantees that $\notlocked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False}$. Furthermore, $\currentthread{t} \ast \locked{\gamma}{t}$ guarantees that \lstinline{owner} is precisely the current thread $t$, so the precondition of \lstinline{unlock} is sufficient to ensure that the \lstinline{assert} will succeed. %But this doesn't seem to be enough to pass !lock in the destructor -- we need something in the true case that's inconsistent with used_threads(\empty). Maybe use some extra ghost state to split not_locked and put half into the invariant. The representation predicate is defined as $\mutex{\gamma}{P}_q \triangleq I(P) \ast \mathsf{tok}_q$. With these definitions, we can prove that each method meets its specification: ... From 257eaa936a59e4c12392381143d43b8cfb328153 Mon Sep 17 00:00:00 2001 From: Ke Du Date: Thu, 3 Sep 2026 02:21:18 -0500 Subject: [PATCH 061/109] rework mutex implementation invariant explanation; now consistent with rocq code --- papers/cpp-mutex/specs.tex | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 0351fb4f..da43e67d 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -596,19 +596,27 @@ \subsubsection{\lstinline{std::mutex}} The standard Iris approach to verifying a spinlock is to define an invariant of the form \[ - \knowInv{}{\begin{gathered} - (\texttt{lock} \mapsto \mathsf{true}) \\ - {}\vee (\texttt{lock} \mapsto \mathsf{false} \ast \token{\gamma} \ast P) - \end{gathered}}, + \knowInv{}{\begin{aligned} + &(\texttt{lock} \mapsto \mathsf{true}) \\ + {}\vee{} &(\texttt{lock} \mapsto \mathsf{false} \ast P \ast \token{\gamma}) + \end{aligned}}, \] -where whenever the lock is not held the invariant contains the exclusive token and the protected resources $P$, and when the lock is held those resources are transferred to the holder. (We use a cancelable invariant instead to support destruction; this gives us a fractional token $\mathsf{tok}_q$ that can be used to deallocate the invariant when $q = 1$.) We add the \lstinline{owner} field to the resources involved, and require that it is the default value $\bot$ when unowned; because ownership of a pointer is exclusive, we can use this in place of the exclusive token, yielding the invariant +where whenever the lock is not held the invariant contains the protected resources $P$ and some exclusive token $\token{\gamma}$, and when the lock is held, these are transferred to the holder. Having $\token{\gamma}$ allows the thread to unlock the mutex: when it opens the invariant, it must be the unlocked branch because there cannot be two copies of $\token{\gamma}$. The mutex invariant is slightly more complex:\footnote{We use a cancelable invariant instead so that the invariant can be "deallocated" and the protected resources unboxed in the mutex destructor.\kedu{The \lstinline|cinv_own| detail should probably be omitted?} } \[ - I(P) \triangleq \knowInv{}{\begin{gathered} - (\texttt{lock} \mapsto \mathsf{true}) \\ - {}\vee (\texttt{lock} \mapsto \mathsf{false} \ast P - \ast \texttt{owner} \mapsto \bot) - \end{gathered}} + I(P) \triangleq \knowInv{}{\begin{aligned} + &(\exists t.\ \ownGhost{}{\authfull t}) \\ + &{}\ast \left(\begin{aligned} + &(\texttt{lock} \mapsto \mathsf{true} + \ast \notlocked{\gamma}{t}) \\ + {}\vee{} &(\texttt{lock} \mapsto \mathsf{false} \ast P + \ast \ownGhost{}{\authfrag \bot} + \ast \texttt{owner} \mapsto \bot) + \end{aligned}\right) + \end{aligned}} \] %don't reuse \gamma, or explain that it's a record + +First, the locking thread $t$ hands over its $\notlocked{\gamma}{t}$ in the invariant. As mentioned before, this ensures the mutex is only destructed when no thread currently locks it. But when $\notlocked{\gamma}{t}$ is stored in the invariant, its argument $t$ is forgotten, so we use a pair of \textit{authoritative} ghost states $\ownGhost{}{\authfull t}$ and $\ownGhost{}{\authfrag t}$ to remedy this: when a thread $t$ locks the mutex, it also receives $\ownGhost{}{\authfrag t}$ from the mutex \kedu{Do we want to explain ghost update?} with the same $t$; the other piece $\ownGhost{}{\authfull t}$ always store the same value as $\ownGhost{}{\authfrag t}$ and is stored in the invariant, so when thread $t$ unlocks the mutex, it can use $\ownGhost{}{\authfrag t}$ to recover $\notlocked{\gamma}{t}$. The $\ownGhost{}{\authfrag t}$ ghost state is also exclusive and does the job of $\token{\gamma}$. Lastly, the \lstinline{owner} field is also a protected resource, and it is required to have the default value $\bot$ when the mutex is unowned. + Now we can define $\locked{\gamma}{t}$ as $\notlocked{\gamma}{t} \ast \texttt{owner} \mapsto t$. We include $\mathsf{not\_locked}$ in $\mathsf{m\_locked}$ because this immediately gives us $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$, and the exclusivity of $\mapsto$ guarantees that $\notlocked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False}$. Furthermore, $\currentthread{t} \ast \locked{\gamma}{t}$ guarantees that \lstinline{owner} is precisely the current thread $t$, so the precondition of \lstinline{unlock} is sufficient to ensure that the \lstinline{assert} will succeed. %But this doesn't seem to be enough to pass !lock in the destructor -- we need something in the true case that's inconsistent with used_threads(\empty). Maybe use some extra ghost state to split not_locked and put half into the invariant. The representation predicate is defined as $\mutex{\gamma}{P}_q \triangleq I(P) \ast \mathsf{tok}_q$. With these definitions, we can prove that each method meets its specification: ... From c7ca3f058ea057878acf4babffa37a317dd3c83e Mon Sep 17 00:00:00 2001 From: Ke Du Date: Thu, 3 Sep 2026 02:23:46 -0500 Subject: [PATCH 062/109] add a comment --- papers/cpp-mutex/specs.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index da43e67d..c4aeacb6 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -617,6 +617,7 @@ \subsubsection{\lstinline{std::mutex}} First, the locking thread $t$ hands over its $\notlocked{\gamma}{t}$ in the invariant. As mentioned before, this ensures the mutex is only destructed when no thread currently locks it. But when $\notlocked{\gamma}{t}$ is stored in the invariant, its argument $t$ is forgotten, so we use a pair of \textit{authoritative} ghost states $\ownGhost{}{\authfull t}$ and $\ownGhost{}{\authfrag t}$ to remedy this: when a thread $t$ locks the mutex, it also receives $\ownGhost{}{\authfrag t}$ from the mutex \kedu{Do we want to explain ghost update?} with the same $t$; the other piece $\ownGhost{}{\authfull t}$ always store the same value as $\ownGhost{}{\authfrag t}$ and is stored in the invariant, so when thread $t$ unlocks the mutex, it can use $\ownGhost{}{\authfrag t}$ to recover $\notlocked{\gamma}{t}$. The $\ownGhost{}{\authfrag t}$ ghost state is also exclusive and does the job of $\token{\gamma}$. Lastly, the \lstinline{owner} field is also a protected resource, and it is required to have the default value $\bot$ when the mutex is unowned. +\kedu{Now \locked{}{} can't have \notlocked{}{} in it, $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$ is probably only provable with the invariant. } Now we can define $\locked{\gamma}{t}$ as $\notlocked{\gamma}{t} \ast \texttt{owner} \mapsto t$. We include $\mathsf{not\_locked}$ in $\mathsf{m\_locked}$ because this immediately gives us $\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False}$, and the exclusivity of $\mapsto$ guarantees that $\notlocked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False}$. Furthermore, $\currentthread{t} \ast \locked{\gamma}{t}$ guarantees that \lstinline{owner} is precisely the current thread $t$, so the precondition of \lstinline{unlock} is sufficient to ensure that the \lstinline{assert} will succeed. %But this doesn't seem to be enough to pass !lock in the destructor -- we need something in the true case that's inconsistent with used_threads(\empty). Maybe use some extra ghost state to split not_locked and put half into the invariant. The representation predicate is defined as $\mutex{\gamma}{P}_q \triangleq I(P) \ast \mathsf{tok}_q$. With these definitions, we can prove that each method meets its specification: ... From e3be152b4b95139fcb46944bc6beb7b5b834a350 Mon Sep 17 00:00:00 2001 From: William Mansky Date: Wed, 30 Sep 2026 10:28:03 -0500 Subject: [PATCH 063/109] started writing up the new approach to not_locked/locked --- .gitignore | 1 + papers/cpp-mutex/specs.tex | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index af745d27..8a421353 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ _RocqProject # Only at the root, since it comes from enabling `dune.disabled` locally /dune +*.bbl diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index c4aeacb6..ce2056c9 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -48,14 +48,15 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \end{figure} %Both this implementation and a more sophisticated one that checks thread IDs meet our specifications. Show the code for both, and note that the latter doesn't meet the standard HeapLang lock specs. We begin by examining \lstinline{std::mutex}, the basic lock class. The class contains methods \lstinline{lock}, \lstinline{unlock}, and \lstinline{try_lock}, as well as a constructor and destructor. The intended behavior is simply to provide mutual exclusion; a simple implementation could be a spinlock as shown in Figure~\ref{mutex-simple}, although more realistic implementations use OS primitives for efficiency. While \lstinline{std::mutex} is the library class corresponding to a standard lock, its informal specification has three restrictions that do not appear in the standard separation logic treatment: -\begin{itemize} +\begin{enumerate} \item When a thread calls \lstinline{unlock}, it must own the mutex (i.e., it must have been the thread that most recently called \lstinline{lock}). \item When a thread calls \lstinline{lock}, it must \emph{not} own the mutex. \item When a mutex is destroyed, it must not be owned by any threads. -\end{itemize} +\end{enumerate} A program that violates any of these conditions has \emph{undefined behavior} according to the library specification, so our program logic must ensure that they are respected. %include checks in toy implementation, or just mention that they could be added? -The representation for a mutex has the same shape as a standard lock: we write $m \mapsto \mutex{\gamma}{P}$ for a mutex at $m$ associated with ghost identifier $\gamma$ and protected resources $P$. However, we must extend the specifications of the mutex operations to enforce the requirements above. One of the original selling points of CSL was that it could naturally verify \emph{daring} concurrency patterns~\cite{csl} in which a lock acquired by one thread is released by another; in fact, in ordinary separation logics every predicate is freely transferable between threads. Fortunately, as mentioned in Section~\ref{brick}, the design of the BRiCk logic supports a non-transferable predicate $\currentthread{t}$ asserting that the current thread is $t$, which we can use to tie resources to a specific thread. +The representation predicate for a mutex has the same shape as a standard lock: we write $m \mapsto \mutex{\gamma}{P}$ for a mutex at $m$ associated with ghost identifier $\gamma$ and protected resources $P$. However, we must extend the specifications of the mutex operations to enforce the requirements above. The third property is the simplest, from a separation logic perspective: we can define a fractional resource $\mathsf{token}_q$ that a thread must give up in order to acquire the mutex, and require full ownership $\mathsf{token}_1$ in the precondition of the destructor. The first two properties are more complicated, since they check whether a thread performing an operation is \emph{the same thread} as one that performed a previous operation. Our basic approach is to define predicates $\notlocked{\gamma}{t}$ and $\locked{\gamma}{t}$ such that a thread $t$ exchanges $\notlocked{\gamma}{t}$ for $\locked{\gamma}{t}$ when acquiring the mutex, and vice versa when releasing it. However, these predicates, like any other CSL predicate, can be transferred between threads when they synchronize: how can we ensure that the thread that owns $\notlocked{\gamma}{t}$ is actually thread $t$? For this, we use BRiCk's non-transferable predicate $\currentthread{t}$ described in Section~\ref{brick}: a thread must own both $\currentthread{t}$ and $\notlocked{\gamma}{t}$ (resp. $\locked{\gamma}{t}$) to acquire (resp. release) the mutex, proving that it is in fact the thread identified by the predicate. +%talk about my_mutexes and the spawn/join specs here or later? \ignore{When we spawn a thread $t$ that will using the mutex, it will receive a predicate $\notlocked{\gamma}{t}$, which it exchanges for another predicate $\locked{\gamma}{t}$ when it locks the mutex. A matching predicate $\usedthreads{\gamma}{T}$ will hold the set of all threads that are currently using the mutex, and the precondition of the destructor will require $\usedthreads{\gamma}{\emptyset}$, ensuring that no thread currently holds the lock. This leads to the following specifications for thread creation and joining: \[\begin{array}{c} \{ \usedthreads{\gamma}{T} \ast P \ast \{\currentthread{t} \ast \notlocked{\gamma}{t} \ast P \}\ f\ \{ \notlocked{\gamma}{t} \ast Q \} \}\\ \texttt{std::thread(}f\texttt{)}\\ \{ \usedthreads{\gamma}{T \cup \{t\}} \ast t \mapsto \mathsf{thread}(Q) \} \vspace{1em} \\ %use wp instead? constructor notation? @@ -68,23 +69,26 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} Formally, these predicates satisfy the following properties: %make this a figure? \begin{mathpar} -\inferrule{t \not\in T}{\usedthreads{\gamma}{T} \vs \usedthreads{\gamma}{T \cup \{t\}} \ast \notlocked{\gamma}{t}} \and -\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vdash t \in T \and -\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vs \usedthreads{\gamma}{T \setminus \{t\}} \and +%\inferrule{t \not\in T}{\usedthreads{\gamma}{T} \vs \usedthreads{\gamma}{T \cup \{t\}} \ast \notlocked{\gamma}{t}} \and +%\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vdash t \in T \and +%\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vs \usedthreads{\gamma}{T \setminus \{t\}} \and \notlocked{\gamma}{t} \ast \notlocked{\gamma}{t} \vdash \mathsf{False} \and \notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False} \and \locked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False} %anything else we need? +\and \mathsf{token}_1 \ast \locked{\gamma}{t} \vdash \mathsf{False} \end{mathpar} %explain a little more, and explain view shifts -This user-tracking pattern is very useful for describing thread-specific properties of mutexes; we will extend it for the following mutex types, and explain its implementation in Section~\ref{login}. +This owner-tracking pattern is very useful for describing thread-specific properties of mutexes; we will extend it for the following mutex types, and explain its implementation in Section~\ref{login}. We can then specify the mutex as follows: \[\begin{array}{c} +% TODO: update constructor with my_mutexes \{\later P\}\\ \texttt{mutex()}\\ \{\exists \gamma.\ \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\emptyset}\} \vspace{1em}\\%constructor notation? +%% TODO: add token(q) or add a fraction to not_locked/locked \{\currentthread{t} \ast m \mapsto_q \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}\}\\ {m\texttt{.lock()}} \\ \{m \mapsto_q \mutex{\gamma}{P}\ast P\ast\locked{\gamma}{t}\} \vspace{1em}\\ \{\currentthread{t} \ast m \mapsto_q \mutex{\gamma}{P}\ast\locked{\gamma}{t} \ast\triangleright P\}\\ {m\texttt{.unlock}()}\\ \{m \mapsto_q \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}\} \vspace{1em} \\ -\{m \mapsto \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\emptyset}\}\\ \sim m()\\ \{P\} %destructor notation? +\{m \mapsto \mutex{\gamma}{P} \ast \mathsf{token}_1\}\\ \sim m()\\ \{P\} %destructor notation? \end{array}\] %note that current_thread is persistent -A thread $t$ exchanges $\notlocked{\gamma}{t}$ for $\locked{\gamma}{t}$ when acquiring the mutex and vice versa when releasing; these predicates are freely transferable, but $\currentthread{t}$ ensures that each thread holds its own predicate when calling \lstinline{lock} or \lstinline{unlock}, and the laws of $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ ensure that no two threads hold the mutex at once and that a thread cannot simultaneously be $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$. Finally, the $\usedthreads{\gamma}{\emptyset}$ in the precondition of the destructor ensures that all threads have been removed from the set of users (and thus cannot hold the mutex) before deallocation. When these specifications are used in proofs, the thread that allocates the mutex will acquire and hold $\usedthreads{\gamma}{T}$, and generate a $\notlocked{\gamma}{t}$ predicate for each thread $t$ participating in the mutex; when those threads terminate, the main thread will recollect all the $\notlocked{}{}$ predicates and remove them from $\usedthreads{}{}$ before the mutex is destroyed. +A thread $t$ exchanges $\notlocked{\gamma}{t}$ for $\locked{\gamma}{t}$ when acquiring the mutex and vice versa when releasing; these predicates are freely transferable, but $\currentthread{t}$ ensures that each thread holds its own predicate when calling \lstinline{lock} or \lstinline{unlock}, and the laws of $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ ensure that no two threads hold the mutex at once and that a thread cannot simultaneously be $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$. Finally, the $\mathsf{token}_1$ in the precondition of the destructor ensures that no threads hold the mutex before deallocation. %When these specifications are used in proofs, the thread that allocates the mutex will acquire and hold $\usedthreads{\gamma}{T}$, and generate a $\notlocked{\gamma}{t}$ predicate for each thread $t$ participating in the mutex; when those threads terminate, the main thread will recollect all the $\notlocked{}{}$ predicates and remove them from $\usedthreads{}{}$ before the mutex is destroyed. \ignore{ \begin{mathpar} @@ -148,7 +152,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \end{figure} Figure~\ref{mutex-ex} shows a simple application of \lstinline{mutex}. The mutex \lstinline{m} protects the variable \lstinline{x}, which is incremented by two separate threads. We sketch a proof with the lock invariant $P \triangleq \exists n.\ \texttt{x} \mapsto n \ast \mathsf{even}(n)$; we could use ghost state to show that \lstinline{x} is exactly 4 at the end of the program, but this is orthogonal to the novelties of our mutex specification. The proof is outlined in Figure~\ref{mutex-ex-proof}. The main thread registers itself and the thread to be spawned with $\mathsf{all\_users}$, obtaining $\mathsf{not\_locked}$ predicates for each of them. When a thread locks \lstinline{m} it exchanges its $\mathsf{not\_locked}$ for $\mathsf{locked}$ and ownership of $P$; unlocking \lstinline{m} performs the same operation in reverse. Finally, the main thread joins with the spawned thread, recollects its $\mathsf{not\_locked}$ predicate, and unregisters both threads from $\mathsf{all\_users}$, so that at the end of \lstinline{main} the precondition of \lstinline{m}'s destructor (namely, $\usedthreads{\gamma}{\emptyset}$) is satisfied, guaranteeing that no thread holds the lock. -\begin{figure}[htb] +\begin{figure}[htb] %% TODO: update this with my_mutexes \begin{lstlisting}[escapeinside = {(*@}{@*)}] unsigned int x = 0; std::mutex m; @@ -502,9 +506,6 @@ \section{Implementing the Mutex Specifications} \subsection{The User Library} \label{login} -TODO cite the Iris reader/writer lock library -TODO mutex is not implemented with the user library yet - All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. The $\mathsf{not\_locked}$ ghost state provides an interface that allows a thread to acquire From 2c26ca3673e006aef8aadf0ce2163e6a727f161c Mon Sep 17 00:00:00 2001 From: William Mansky Date: Wed, 30 Sep 2026 13:39:14 -0500 Subject: [PATCH 064/109] started explaining my_mutexes --- papers/cpp-mutex/specs.tex | 64 +++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index ce2056c9..55167efb 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -72,7 +72,8 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} %\inferrule{t \not\in T}{\usedthreads{\gamma}{T} \vs \usedthreads{\gamma}{T \cup \{t\}} \ast \notlocked{\gamma}{t}} \and %\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vdash t \in T \and %\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vs \usedthreads{\gamma}{T \setminus \{t\}} \and -\notlocked{\gamma}{t} \ast \notlocked{\gamma}{t} \vdash \mathsf{False} \and \notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False} \and \locked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False} %anything else we need? +\notlocked{\gamma}{t} \ast \notlocked{\gamma}{t} \vdash \mathsf{False} \and %\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False} \and +\locked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False} %anything else we need? \and \mathsf{token}_1 \ast \locked{\gamma}{t} \vdash \mathsf{False} \end{mathpar} %explain a little more, and explain view shifts This owner-tracking pattern is very useful for describing thread-specific properties of mutexes; we will extend it for the following mutex types, and explain its implementation in Section~\ref{login}. @@ -503,43 +504,34 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} \section{Implementing the Mutex Specifications} Our specifications of C++ mutexes capture the informal specifications of the mutexes and can be used to verify client programs. Ideally, we would next show that standard library implementations actually meet our specifications. However, the widely used implementations of the C++ standard library would be difficult to verify---they use Pthread implementations of the corresponding mutex types, which have nearly the same specifications as the standard library mutexes, and the Pthread implementations in turn make heavy use of operating system primitives with complicated low-level specifications (e.g., Linux's \lstinline{futex}). In this section, we instead verify simple and portable implementations of the mutexes that synchronize using atomic operations instead of OS primitives. We first describe the ghost state construction that underlies the $\mathsf{locked}$ and $\mathsf{not\_locked}$ predicates for each mutex, and then show that our simple implementation of each mutex meets the specifications of the previous section. -\subsection{The User Library} +\subsection{The User(?) Library} \label{login} -All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. - -The $\mathsf{not\_locked}$ ghost state provides an interface that allows a thread to acquire -locked resources at most once regardless of the mutex kinds. Each flavor of mutex -is associated with a disjoint set of thread IDs, managed by one -authoritative piece \allusers\ and fragment pieces \notlocked{\gamma}{t}: -\begin{align*} - \usedthreads{\gamma}{T} - &\eqdef \ownGhost{\gamma}{\mathord{\bullet}T} \\ - % \mathsf{users}^{\gamma}(T) - % &\eqdef \mathsf{own}^{\gamma}(\mathord{\circ}T), \\ - \notlocked{\gamma}{t} - &\eqdef \ownGhost{\gamma}{\mathord{\circ}\{t\}} -\end{align*} -The \allusers{T} ghost state tracks all "logged-in" threads $T$. Each thread with thread ID $t$ can get -a \notlocked{\gamma}{t} handle for that mutex with the \texttt{login} rule, and the handle is -unique for each thread (not-locked-unique). %write out all these rules! - -The mutex invariants are slightly -different for different kinds of mutexes, but they all ensure that a thread -must trade in its unique \notlocked{\gamma}{t} handle to acquire protected resources. -This ensures that a thread can't acquire protected resources if it already has -some, and unsafe programs that calls \texttt{mutex.lock()} or -\texttt{shared\_mutex.lock\_shared()} twice consecutively in a single thread -would get stuck. Notably, for recursive mutexes, if a thread already has the -resource, it is safe to \texttt{lock()} again, and the thread simply does not -exchange anything with the invariant. - -A thread can also "logout" and turn in its \notlocked{\gamma}{t} handle. Deallocation -of any kind of mutex requires no "logged-in" users -($\allusers^{\gamma}(\varnothing)$), enforcing that any allocated -\notlocked{\gamma}{t} handle is deallocated with \texttt{logout} -(\texttt{all-not\_locked-logged-out}), and so the mutex is not held -by any thread, thus safe to deallocate. +\newcommand{\mutexperm}[2]{\ensuremath{\mathsf{mutex\_perm}_{#1}(#2)}} + +All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{m\_locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. + +In order to acquire a non-recursive mutex, a thread needs to prove that it does not own that mutex: in separation-logic terms, it needs to own a resource demonstrating that it is not the current holder of the mutex. This means that each thread must own, or be able to create, a not-the-owner resource (call it a ``mutex permission'') %name? +for every mutex it does not currently hold. We model this using disjoint infinite sets as ghost state: each thread $t$ is created with a resource $\mutexperm{t}{\top}$, where $\top$ is the universe of possible mutex names, and $\ownGhost{}{\mutexperm{t}{A \uplus B}} \dashv\vdash \ownGhost{}{\mutexperm{t}{A}} \ast \ownGhost{}{\mutexperm{t}{B}}$ (where $A \uplus B$ is only defined if $A$ and $B$ are disjoint). Then we can implement $\notlocked{\gamma}{t}$ as precisely $\ownGhost{}{\mutexperm{t}{\{\gamma\}}}$. %update with token ownership +When a thread wants to interact with a mutex $\gamma$, it detaches $\mutexperm{t}{\{\gamma\}}$ from its set and uses it as permission to acquire $\gamma$; disjointness guarantees that each thread can only have one such permission. +%contrast with the less-good user library approach? + +%The mutex invariants are slightly +%different for different kinds of mutexes, but they all ensure that a thread +%must trade in its unique \notlocked{\gamma}{t} handle to acquire protected resources. +%This ensures that a thread can't acquire protected resources if it already has +%some, and unsafe programs that calls \texttt{mutex.lock()} or +%\texttt{shared\_mutex.lock\_shared()} twice consecutively in a single thread +%would get stuck. Notably, for recursive mutexes, if a thread already has the +%resource, it is safe to \texttt{lock()} again, and the thread simply does not +%exchange anything with the invariant. + +%A thread can also "logout" and turn in its \notlocked{\gamma}{t} handle. Deallocation +%of any kind of mutex requires no "logged-in" users +%($\allusers^{\gamma}(\varnothing)$), enforcing that any allocated +%\notlocked{\gamma}{t} handle is deallocated with \texttt{logout} +%(\texttt{all-not\_locked-logged-out}), and so the mutex is not held +%by any thread, thus safe to deallocate. %specific locked instances here or below? From 8c494e5ec181a0d5b78d5071f6b85480603ba8da Mon Sep 17 00:00:00 2001 From: William Mansky Date: Wed, 30 Sep 2026 14:40:21 -0500 Subject: [PATCH 065/109] case study code --- papers/cpp-mutex/stdcpp.tex | 44 +++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/papers/cpp-mutex/stdcpp.tex b/papers/cpp-mutex/stdcpp.tex index b522c68d..04ab9ac0 100644 --- a/papers/cpp-mutex/stdcpp.tex +++ b/papers/cpp-mutex/stdcpp.tex @@ -449,10 +449,50 @@ \subsection{\lstinline{unique_lock}} As discussed in Section~\ref{basic-lockable}, the \lstinline{BasicLockable} requirement does not explicitly require that \lstinline{lock} lead to mutual exclusion, and indeed recursive mutexes can be \lstinline{lock}ed multiple times without being \lstinline{unlock}ed. This interacts in an interesting and useful way with \lstinline{unique_lock}'s acquisition tracking (the $\mathsf{held}$ predicate in our specifications): each \lstinline{unique_lock} can only acquire its mutex at most once, but we can construct multiple \lstinline{unique_lock}s around a single recursive mutex, each of which manages a single acquisition. %Is this equally true of lock_guard? We could call unlock once manually to illustrate the point. The code in Figure~\ref{unique-recursive} shows how this can be used. When \lstinline{answer} is called, it acquires the recursive mutex \lstinline{m} by constructing a \lstinline{unique_lock} around it; it then calls \lstinline{inc}, which constructs another \lstinline{unique_lock} that acquires \lstinline{m} a second time. When each method returns, its \lstinline{unique_lock} is destroyed and \lstinline{m} is released once, so that \lstinline{m} is no longer locked after \lstinline{answer} returns. Our specifications can be used to verify this program. %proof outline? -\section{Case Studies?} -\input{ring_buffer.tex} +\section{Case Study: Mutex Encapsulation} +%\input{ring_buffer.tex} %TODO: find or make some interesting examples +We have seen that enforcing the requirements of C++ mutexes requires the management of thread-specific permission predicates that must be transferred between threads and locks. We have demonstrated that this is possible in simple examples of each mutex type, but we also want to know that our approach scales to programs with multiple mutex-carrying objects. %rephrase +There are at least two possible pitfalls. First, it might be difficult for a thread to acquire two different mutexes, because we need to manually prove that the second mutex is not the same as the first. Second and more subtly, thread-specific predicates might leak through layers of abstraction. Our mutex specifications must include predicates like $\notlocked{\gamma}{t}$ that explicitly mention thread IDs, but a data structure that uses a mutex internally should not need to expose thread IDs in its interface; it should be possible to share the data structure among threads exactly as one would a non-mutex-based implementation, without extra registration steps. In this section, we verify a case study showing that we avoid both these pitfalls, suggesting that our mutex specifications can indeed be used for modular verification. + +\begin{figure}[htb] +\begin{lstlisting} +class C { +private: + unsigned long balance; + std::recursive_mutex mut; // protects balance + +public: + explicit C(unsigned long initial_balance) : balance(initial_balance) {} + + void incr_balance(unsigned long i) { + std::lock_guard lg(mut); + balance += i; + } + + void transfer_to(C target, unsigned long i) { + std::lock_guard lg(mut); + balance -= i; + target.incr_balance(i); + } + +} + +int main() { + C from{4}; + C to{2}; + from.transfer(to, 3); + return 0; +} +\end{lstlisting} +\caption{The code of our case study} +\label{transfer} +\end{figure} + + +The code of our case study is shown in Figure~\ref{transfer}. + \section{BRiCk Formalization} \section{Conclusion and Future Work} From 1e04a214d29a4096285c76aa7ee3feaccc8df20a Mon Sep 17 00:00:00 2001 From: William Mansky Date: Wed, 30 Sep 2026 14:53:22 -0500 Subject: [PATCH 066/109] set out questions for case study --- papers/cpp-mutex/stdcpp.tex | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/papers/cpp-mutex/stdcpp.tex b/papers/cpp-mutex/stdcpp.tex index 04ab9ac0..135dbd4f 100644 --- a/papers/cpp-mutex/stdcpp.tex +++ b/papers/cpp-mutex/stdcpp.tex @@ -458,32 +458,30 @@ \section{Case Study: Mutex Encapsulation} \begin{figure}[htb] \begin{lstlisting} -class C { +class Account { private: unsigned long balance; std::recursive_mutex mut; // protects balance public: - explicit C(unsigned long initial_balance) : balance(initial_balance) {} + explicit Account(unsigned long initial_balance) : balance(initial_balance) {} void incr_balance(unsigned long i) { std::lock_guard lg(mut); balance += i; } - void transfer_to(C target, unsigned long i) { + void transfer_to(Account target, unsigned long i) { std::lock_guard lg(mut); balance -= i; target.incr_balance(i); } - } int main() { - C from{4}; - C to{2}; - from.transfer(to, 3); - return 0; + Account from{4}; + Account to{2}; + from.transfer_to(to, 3); } \end{lstlisting} \caption{The code of our case study} @@ -491,7 +489,13 @@ \section{Case Study: Mutex Encapsulation} \end{figure} -The code of our case study is shown in Figure~\ref{transfer}. +The code of our case study is shown in Figure~\ref{transfer}. Each object of class \lstinline{Account} has a \lstinline{balance} protected by a mutex (in this code we use a \lstinline{recursive_mutex}, but any form of mutex would work). %make generic? +The \lstinline{transfer_to} method decrements the host object's %terminology? +balance and increments the target object's balance, acquiring both mutexes in the process. Our questions are then: +\begin{enumerate} +\item How do we prove that we can acquire \lstinline{target}'s mutex, given that we already hold the host's mutex? +\item Can we use a representation predicate for \lstinline{Account} that is freely transferable between threads and does not take a thread ID as a parameter? +\end{enumerate} \section{BRiCk Formalization} From 643e3fd6f8a6e1e04f4d0b857c0b6cfcdf795975 Mon Sep 17 00:00:00 2001 From: William Mansky Date: Thu, 1 Oct 2026 16:26:29 -0500 Subject: [PATCH 067/109] updated mutex to new approach --- papers/cpp-mutex/specs.tex | 105 +++++++++++++++++------------------- papers/cpp-mutex/stdcpp.tex | 33 +++++++++--- 2 files changed, 76 insertions(+), 62 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 55167efb..bef5212b 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -18,8 +18,8 @@ \section{Three Flavors of Mutex} \NewDocumentCommand{\allusers}{g}{% \ensuremath{\mathsf{all\_users}\IfValueT{#1}{(#1)}}} \newcommand{\usedthreads}[2]{\ensuremath{\allusers_{#1}(#2)}} -\newcommand{\notlocked}[2]{\ensuremath{\mathsf{not\_locked}_{#1}(#2)}} -\newcommand{\locked}[2]{\ensuremath{\mathsf{m\_locked}_{#1}(#2)}} +\newcommand{\notlocked}[3]{\ensuremath{\mathsf{not\_locked}_{#1}(#2, #3)}} +\newcommand{\locked}[3]{\ensuremath{\mathsf{m\_locked}_{#1}(#2, #3)}} \newcommand{\smlocked}[2]{\ensuremath{\mathsf{sm\_locked}_{#1}(#2)}} \newcommand{\rmlocked}[2]{\ensuremath{\mathsf{rm\_locked}_{#1}(#2)}} \newcommand{\mutex}[2]{\ensuremath{\mathsf{mutex}_{#1}(#2)}} @@ -27,6 +27,9 @@ \section{Three Flavors of Mutex} \newcommand{\rmutex}[2]{\ensuremath{\mathsf{rmutex}_{#1}(#2)}} \newcommand{\smreaderlocked}[3]{\ensuremath{\mathsf{sm\_reader\_locked}_{#1}(#2, #3)}} \newcommand{\smutex}[2]{\ensuremath{\mathsf{smutex}_{#1}(#2)}} +\newcommand{\token}[2]{\ensuremath{\mathsf{token}_{#1}(#2)}} +\newcommand{\mutexperm}[2]{\ensuremath{\mathsf{mutex\_perm}_{#1}(#2)}} +\newcommand{\mutexname}[1]{\ensuremath{\mathsf{name}(#1)}} \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \label{mutex-spec} @@ -55,41 +58,32 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \end{enumerate} A program that violates any of these conditions has \emph{undefined behavior} according to the library specification, so our program logic must ensure that they are respected. %include checks in toy implementation, or just mention that they could be added? -The representation predicate for a mutex has the same shape as a standard lock: we write $m \mapsto \mutex{\gamma}{P}$ for a mutex at $m$ associated with ghost identifier $\gamma$ and protected resources $P$. However, we must extend the specifications of the mutex operations to enforce the requirements above. The third property is the simplest, from a separation logic perspective: we can define a fractional resource $\mathsf{token}_q$ that a thread must give up in order to acquire the mutex, and require full ownership $\mathsf{token}_1$ in the precondition of the destructor. The first two properties are more complicated, since they check whether a thread performing an operation is \emph{the same thread} as one that performed a previous operation. Our basic approach is to define predicates $\notlocked{\gamma}{t}$ and $\locked{\gamma}{t}$ such that a thread $t$ exchanges $\notlocked{\gamma}{t}$ for $\locked{\gamma}{t}$ when acquiring the mutex, and vice versa when releasing it. However, these predicates, like any other CSL predicate, can be transferred between threads when they synchronize: how can we ensure that the thread that owns $\notlocked{\gamma}{t}$ is actually thread $t$? For this, we use BRiCk's non-transferable predicate $\currentthread{t}$ described in Section~\ref{brick}: a thread must own both $\currentthread{t}$ and $\notlocked{\gamma}{t}$ (resp. $\locked{\gamma}{t}$) to acquire (resp. release) the mutex, proving that it is in fact the thread identified by the predicate. -%talk about my_mutexes and the spawn/join specs here or later? -\ignore{When we spawn a thread $t$ that will using the mutex, it will receive a predicate $\notlocked{\gamma}{t}$, which it exchanges for another predicate $\locked{\gamma}{t}$ when it locks the mutex. A matching predicate $\usedthreads{\gamma}{T}$ will hold the set of all threads that are currently using the mutex, and the precondition of the destructor will require $\usedthreads{\gamma}{\emptyset}$, ensuring that no thread currently holds the lock. This leads to the following specifications for thread creation and joining: -\[\begin{array}{c} -\{ \usedthreads{\gamma}{T} \ast P \ast \{\currentthread{t} \ast \notlocked{\gamma}{t} \ast P \}\ f\ \{ \notlocked{\gamma}{t} \ast Q \} \}\\ \texttt{std::thread(}f\texttt{)}\\ \{ \usedthreads{\gamma}{T \cup \{t\}} \ast t \mapsto \mathsf{thread}(Q) \} \vspace{1em} \\ %use wp instead? constructor notation? -\{ \usedthreads{\gamma}{T} \ast t \mapsto \mathsf{thread}(Q) \}\\ t\texttt{.join()}\\ \{ \usedthreads{\gamma}{T \setminus \{t\}} \ast Q \} -\end{array} -\] -When a thread is spawned running a function $f$, it receives in addition to its precondition $P$ the assertions $\currentthread{t}$ and $\notlocked{\gamma}{t}$, and $\notlocked{\gamma}{t}$ must again hold when it returns. Meanwhile, the parent thread adds $t$ to the set of users of the mutex, and receives a thread handle $\mathsf{thread}(Q)$ that can be used to join with the child thread. When \lstinline{join} is called, $t$ is removed from $\mathsf{all\_users}$ and the caller receives the postcondition $Q$ of the terminated thread. - -The $\mathsf{locked}$ and $\mathsf{not\_locked}$ predicates must also satisfy the following properties, guaranteeing that every user of the mutex has been registered, $\mathsf{locked}$ implies mutual exclusion, and each thread holds at most one of the two predicates:} -Formally, these predicates satisfy the following properties: +The representation predicate for a mutex has the same shape as a standard lock: we write $m \mapsto \mutex{\gamma}{P}$ for a mutex at $m$ associated with ghost identifier $\gamma$ and protected resources $P$. However, we must extend the specifications of the mutex operations to enforce the requirements above. The third property is the simplest from a separation logic perspective: we can define a fractional resource $\token{}{q}$ that a thread must give up in order to acquire the mutex, and require full ownership $\token{}{1}$ in the precondition of the destructor. The first two properties are more complicated, since they check whether a thread performing an operation is \emph{the same thread} as one that performed a previous operation. Our basic approach is to define predicates $\notlocked{\gamma}{t}{q}$ and $\locked{\gamma}{t}{q}$ such that a thread $t$ exchanges $\notlocked{\gamma}{t}{q}$ for $\locked{\gamma}{t}{q}$ when acquiring the mutex, and vice versa when releasing it. However, these predicates, like any other CSL predicate, can be transferred between threads when they synchronize: how can we ensure that the thread that owns $\notlocked{\gamma}{t}{q}$ is actually thread $t$? For this, we use BRiCk's non-transferable predicate $\currentthread{t}$ described in Section~\ref{brick}: a thread must own both $\currentthread{t}$ and $\notlocked{\gamma}{t}{q}$ (resp. $\locked{\gamma}{t}{q}$) to acquire (resp. release) the mutex, proving that it is in fact the thread identified by the predicate. These predicates satisfy the following properties: %make this a figure? \begin{mathpar} -%\inferrule{t \not\in T}{\usedthreads{\gamma}{T} \vs \usedthreads{\gamma}{T \cup \{t\}} \ast \notlocked{\gamma}{t}} \and -%\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vdash t \in T \and -%\usedthreads{\gamma}{T} \ast \notlocked{\gamma}{t} \vs \usedthreads{\gamma}{T \setminus \{t\}} \and -\notlocked{\gamma}{t} \ast \notlocked{\gamma}{t} \vdash \mathsf{False} \and %\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False} \and -\locked{\gamma}{t_1} \ast \locked{\gamma}{t_2} \vdash \mathsf{False} %anything else we need? -\and \mathsf{token}_1 \ast \locked{\gamma}{t} \vdash \mathsf{False} +\notlocked{\gamma}{t}{q_1} \ast \notlocked{\gamma}{t}{q_2} \vdash \mathsf{False} \and %\notlocked{\gamma}{t} \ast \locked{\gamma}{t} \vdash \mathsf{False} \and +\locked{\gamma}{t_1}{q_1} \ast \locked{\gamma}{t_2}{q_2} \vdash \mathsf{False} +\and \token{\gamma}{1} \ast \locked{\gamma}{t}{q} \vdash \mathsf{False} %anything else we need? \end{mathpar} %explain a little more, and explain view shifts -This owner-tracking pattern is very useful for describing thread-specific properties of mutexes; we will extend it for the following mutex types, and explain its implementation in Section~\ref{login}. -We can then specify the mutex as follows: +How does a thread obtain $\notlocked{\gamma}{t}{q}$ to begin with? We could explicitly specify a set of threads that are allowed to use a mutex when it is created, but we may not know this set of threads in advance and it may even be unbounded (e.g., if we spawn threads in a loop). Instead, we make it possible for a thread $t$ to \emph{register} itself with an existing mutex $\gamma$ and obtain $\notlocked{\gamma}{t}{q}$, as long as $t$ can guarantee that it is not already registered with $\gamma$. Each thread holds a set of as-yet-unregistered mutex names $\mutexperm{t}{A}$, where $A$ is initially the universe of possible mutex names $\top$. When $t$ wishes to register with a mutex $\gamma$, it removes the name of $\gamma$ from $A$ and exchanges it (along with a share of $\mathsf{token}$) for $\mathsf{not\_locked}$: +\[ \mutexperm{t}{\mutexname{\gamma}} \ast \token{\gamma}{q} \dashv \vdash \notlocked{\gamma}{t}{q} \] +When a thread is finished using the mutex (and does not currently hold it), it can perform the same operation in reverse to recover $\mutexperm{t}{\mutexname{\gamma}}$ and $\token{\gamma}{q}$. +This name-tracking pattern is very useful for describing thread-specific properties of mutexes; we will extend it for the following mutex types, and explain its implementation in Section~\ref{login}. + +Using the $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ predicates, we can specify the mutex as follows: \[\begin{array}{c} % TODO: update constructor with my_mutexes -\{\later P\}\\ \texttt{mutex()}\\ \{\exists \gamma.\ \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\emptyset}\} \vspace{1em}\\%constructor notation? +\{\later P\}\\ \texttt{mutex()}\\ \{m.\ \exists \gamma.\ m \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1}\} \vspace{1em}\\%constructor notation? %% TODO: add token(q) or add a fraction to not_locked/locked -\{\currentthread{t} \ast m \mapsto_q \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}\}\\ {m\texttt{.lock()}} \\ \{m \mapsto_q \mutex{\gamma}{P}\ast P\ast\locked{\gamma}{t}\} \vspace{1em}\\ - \{\currentthread{t} \ast m \mapsto_q \mutex{\gamma}{P}\ast\locked{\gamma}{t} - \ast\triangleright P\}\\ {m\texttt{.unlock}()}\\ - \{m \mapsto_q \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}\} \vspace{1em} \\ -\{m \mapsto \mutex{\gamma}{P} \ast \mathsf{token}_1\}\\ \sim m()\\ \{P\} %destructor notation? +\{\currentthread{t} \ast m \mapsto_\pi \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}{q}\}\\ {\texttt{m.lock()}} \\ \{m \mapsto_\pi \mutex{\gamma}{P}\ast P\ast\locked{\gamma}{t}{q}\} \vspace{1em}\\ + \{\currentthread{t} \ast m \mapsto_\pi \mutex{\gamma}{P}\ast\locked{\gamma}{t}{q} + \ast\triangleright P\}\\ {\texttt{m.unlock}()}\\ + \{m \mapsto_\pi \mutex{\gamma}{P}\ast\notlocked{\gamma}{t}{q}\} \vspace{1em} \\ +\{m \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1}\}\\ \texttt{m.}\!\!\sim\!\texttt{mutex()}\\ \{P\} %destructor notation? \end{array}\] %note that current_thread is persistent -A thread $t$ exchanges $\notlocked{\gamma}{t}$ for $\locked{\gamma}{t}$ when acquiring the mutex and vice versa when releasing; these predicates are freely transferable, but $\currentthread{t}$ ensures that each thread holds its own predicate when calling \lstinline{lock} or \lstinline{unlock}, and the laws of $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ ensure that no two threads hold the mutex at once and that a thread cannot simultaneously be $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$. Finally, the $\mathsf{token}_1$ in the precondition of the destructor ensures that no threads hold the mutex before deallocation. %When these specifications are used in proofs, the thread that allocates the mutex will acquire and hold $\usedthreads{\gamma}{T}$, and generate a $\notlocked{\gamma}{t}$ predicate for each thread $t$ participating in the mutex; when those threads terminate, the main thread will recollect all the $\notlocked{}{}$ predicates and remove them from $\usedthreads{}{}$ before the mutex is destroyed. +A thread $t$ exchanges $\notlocked{\gamma}{t}{q}$ for $\locked{\gamma}{t}{q}$ when acquiring the mutex and vice versa when releasing; $\currentthread{t}$ ensures that each thread holds its own predicate when calling \lstinline{lock} or \lstinline{unlock}%, and the laws of $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ ensure that no two threads hold the mutex at once and that a thread cannot simultaneously be $\mathsf{m\_locked}$ and $\mathsf{not\_locked}$ +. Finally, $\token{\gamma}{1}$ in the precondition of the destructor ensures that no threads hold the mutex before deallocation. %When these specifications are used in proofs, the thread that allocates the mutex will acquire and hold $\usedthreads{\gamma}{T}$, and generate a $\notlocked{\gamma}{t}$ predicate for each thread $t$ participating in the mutex; when those threads terminate, the main thread will recollect all the $\notlocked{}{}$ predicates and remove them from $\usedthreads{}{}$ before the mutex is destroyed. \ignore{ \begin{mathpar} @@ -151,42 +145,43 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \caption{An example using \lstinline{std::mutex}} \label{mutex-ex} \end{figure} -Figure~\ref{mutex-ex} shows a simple application of \lstinline{mutex}. The mutex \lstinline{m} protects the variable \lstinline{x}, which is incremented by two separate threads. We sketch a proof with the lock invariant $P \triangleq \exists n.\ \texttt{x} \mapsto n \ast \mathsf{even}(n)$; we could use ghost state to show that \lstinline{x} is exactly 4 at the end of the program, but this is orthogonal to the novelties of our mutex specification. The proof is outlined in Figure~\ref{mutex-ex-proof}. The main thread registers itself and the thread to be spawned with $\mathsf{all\_users}$, obtaining $\mathsf{not\_locked}$ predicates for each of them. When a thread locks \lstinline{m} it exchanges its $\mathsf{not\_locked}$ for $\mathsf{locked}$ and ownership of $P$; unlocking \lstinline{m} performs the same operation in reverse. Finally, the main thread joins with the spawned thread, recollects its $\mathsf{not\_locked}$ predicate, and unregisters both threads from $\mathsf{all\_users}$, so that at the end of \lstinline{main} the precondition of \lstinline{m}'s destructor (namely, $\usedthreads{\gamma}{\emptyset}$) is satisfied, guaranteeing that no thread holds the lock. +Figure~\ref{mutex-ex} shows a simple application of \lstinline{mutex}. The mutex \lstinline{m} protects the variable \lstinline{x}, which is incremented by two separate threads. We sketch a proof with the lock invariant $P \triangleq \exists n.\ \texttt{x} \mapsto n \ast \mathsf{even}(n)$; we could use ghost state to show that \lstinline{x} is exactly 4 at the end of the program, but this is orthogonal to the novelties of our mutex specification. The proof is outlined in Figure~\ref{mutex-ex-proof}. When the main thread spawns a new thread executing \lstinline{double_incr}, it gives the new thread partial ownership of the mutex and its token; it does not need to give up $\currentthread{t_1}$ or $\mutexperm{t}{\top}$, because the new thread automatically receives its own thread-specific resources. Before calling \lstinline{m.lock}, each thread registers with the mutex, exchanging $\mutexperm{}{\mutexname{\gamma}}$ and a piece of the token for a $\mathsf{not\_locked}$ predicate. When a thread locks \lstinline{m} it exchanges its $\mathsf{not\_locked}$ for $\mathsf{m\_locked}$ and ownership of $P$; unlocking \lstinline{m} performs the same operation in reverse. After each thread finishes using \lstinline{m}, it unregisters and recovers its token and mutex permission. +Finally, the main thread joins with the spawned thread and recollects its shares of the mutex and token; the full $\token{\gamma}{1}$ guarantees that no thread holds the lock, satisfying the precondition of \lstinline{m}'s destructor. -\begin{figure}[htb] %% TODO: update this with my_mutexes +\begin{figure}[htb] \begin{lstlisting}[escapeinside = {(*@}{@*)}] unsigned int x = 0; std::mutex m; - (*@ $\{ \texttt{m} \mapsto_q \mutex{\gamma}{P} \ast \notlocked{\gamma}{\{t_2\}}$ @*) + (*@ $\{ \currentthread{t} \ast \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \token{\gamma}{q} \ast \mutexperm{t}{\mutexname{\gamma}}$ @*) void double_incr() { - (*@ $\{ \currentthread{t_2} \ast \texttt{m} \mapsto_q \mutex{\gamma}{P} \ast \notlocked{\gamma}{t_2} \}$ @*) + (*@ $\Rightarrow \{ \currentthread{t} \ast \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \notlocked{\gamma}{t}{q} \}$ @*) m.lock(); - (*@ $\{ ... \ast P \ast \locked{\gamma}{t_2} \}$ @*) + (*@ $\{ ... \ast P \ast \locked{\gamma}{t}{q} \}$ @*) x++; x++; - (*@ $\{ ... \ast P \ast \locked{\gamma}{t_2} \}$ @*) + (*@ $\{ ... \ast P \ast \locked{\gamma}{t}{q} \}$ @*) m.unlock(); - (*@ $\{ \texttt{m} \mapsto_q \mutex{\gamma}{P} \ast \notlocked{\gamma}{t_2} \}$ @*) + (*@ $\{ \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \notlocked{\gamma}{t}{q} \} \Rightarrow$ @*) } - (*@ $\{ \texttt{m} \mapsto_q \mutex{\gamma}{P} \ast \notlocked{\gamma}{t_2} \}$ @*) + (*@ $\{ \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \token{\gamma}{q} \ast \mutexperm{t}{\mutexname{\gamma}} \}$ @*) void main() { - (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\emptyset} \} \vs$ @*) - (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\{t_1, t_2\}} \ast \notlocked{\gamma}{t_1} \ast \notlocked{\gamma}{t_2} \}$ @*) + (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1} \ast \mutexperm{t}{\top} \}$ @*) std::thread t(double_incr); - (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\{t_1, t_2\}} \ast \notlocked{\gamma}{t_1} \}$ @*) + (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \token{\gamma}{.5} \ast \mutexperm{t}{\top} \} \Rightarrow$ @*) + (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \mutexperm{t}{\top \setminus \{\mutexname{\gamma}\}} \ast \notlocked{\gamma}{t_1}{.5} \}$ @*) m.lock(); - (*@ $\{ ... \ast P \ast \locked{\gamma}{t_1} \}$ @*) + (*@ $\{ ... \ast P \ast \locked{\gamma}{t_1}{.5} \}$ @*) x += 2; - (*@ $\{ ... \ast P \ast \locked{\gamma}{t_1} \}$ @*) + (*@ $\{ ... \ast P \ast \locked{\gamma}{t_1}{.5} \}$ @*) m.unlock(); - (*@ $\{ ... \ast \notlocked{\gamma}{t_1} \}$ @*) + (*@ $\{ ... \ast \notlocked{\gamma}{t_1}{.5} \} \Rightarrow$ @*) + (*@ $\{ ... \ast \token{\gamma}{.5} \ast \mutexperm{t}{\top} \}$ @*) t.join(); - (*@ $\{ \texttt{m} \mapsto \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\{t_1, t_2\}} \ast \notlocked{\gamma}{t_1} \ast \notlocked{\gamma}{t_2} \} \vs$ @*) - (*@ $\{ \texttt{m} \mapsto \mutex{\gamma}{P} \ast \usedthreads{\gamma}{\emptyset} \}$ @*) + (*@ $\{ \texttt{m} \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1} \ast \mutexperm{t}{\top} \}$ @*) } -\end{lstlisting}%say something about knowing t2 before creating the thread? +\end{lstlisting} \caption{The proof outline for the example} \label{mutex-ex-proof} \end{figure} @@ -228,8 +223,8 @@ \subsection{\texorpdfstring{\lstinline{std::recursive_mutex}}{std::recursive\_mu Using $\mathsf{holds}$, we can specify \lstinline{lock} and \lstinline{unlock} as simply increasing and decreasing $n$, respectively: \[\begin{array}{c} -\{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n}{P}{a}\}\\ {m\texttt{.lock()}} \\ \{m \mapsto_q \rmutex{\gamma}{P}\ast \exists a'.\ \mathsf{acquire}(n, a, a') \ast \acquirable{\gamma}{t}{n+1}{P}{a'}\} \vspace{1em}\\ - \{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n+1}{P}{a}\}\\ {m\texttt{.unlock}()}\\ +\{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n}{P}{a}\}\\ {\texttt{m.lock()}} \\ \{m \mapsto_q \rmutex{\gamma}{P}\ast \exists a'.\ \mathsf{acquire}(n, a, a') \ast \acquirable{\gamma}{t}{n+1}{P}{a'}\} \vspace{1em}\\ + \{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n+1}{P}{a}\}\\ {\texttt{m.unlock}()}\\ \{m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n}{P}{a}\} \end{array}\] where $\mathsf{acquire}(n, a, a') \triangleq (n > 0 \Rightarrow a' = a)$ allows the argument $a$ to change arbitrarily if the caller of \lstinline{lock} did not already own the lock, but enforces that $a$ is unchanged if the caller did already own the lock. Other than that, these specifications are entirely agnostic to whether any given call is the first \lstinline{lock} or last \lstinline{unlock} on the mutex: we can thus reason about a method that acquires a recursive mutex, manipulates the associated resources, and releases the mutex without ever having to do case analysis on whether the caller already owned the mutex. @@ -376,20 +371,20 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} \[ \begin{array}{@{}c@{}} \{\currentthread{t} \ast m \mapsto_q \smutex{\gamma}{P}\ast\notlocked{\gamma}{t}\} \\ - m\texttt{.lock()} \\[-1pt] + \texttt{m.lock()} \\[-1pt] \{m \mapsto_q \smutex{\gamma}{P}\ast P(1)\ast\smlocked{\gamma}{t}\} \\[1.8em] \{\currentthread{t} \ast m \mapsto_q \smutex{\gamma}{P}\ast\smlocked{\gamma}{t}\ast\triangleright P(1)\}\\ - m\texttt{.unlock()} \\[-1pt] + \texttt{m.unlock()} \\[-1pt] \{m \mapsto_q \smutex{\gamma}{P}\ast\notlocked{\gamma}{t}\} \\[1.8em] \{\currentthread{t} \ast m \mapsto_q \smutex{\gamma}{P}\ast\notlocked{\gamma}{t}\}\\ - m\texttt{.lock\_shared()} \\[-1pt] + \texttt{m.lock\_shared()} \\[-1pt] \{m \mapsto_q \smutex{\gamma}{P}\ast\exists q_P.\;P(q_P)\ast\smreaderlocked{\gamma}{t}{q_P}\} \\[1.8em] \{\currentthread{t} \ast m \mapsto_q \smutex{\gamma}{P}\ast\smreaderlocked{\gamma}{t}{q_P} \ast\triangleright P(q_P)\} \\[-1pt] - m\texttt{.unlock\_shared()} \\ + \texttt{m.unlock\_shared()} \\ \{m \mapsto_q \smutex{\gamma}{P}\ast\notlocked{\gamma}{t}\} \end{array} \] @@ -504,11 +499,9 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} \section{Implementing the Mutex Specifications} Our specifications of C++ mutexes capture the informal specifications of the mutexes and can be used to verify client programs. Ideally, we would next show that standard library implementations actually meet our specifications. However, the widely used implementations of the C++ standard library would be difficult to verify---they use Pthread implementations of the corresponding mutex types, which have nearly the same specifications as the standard library mutexes, and the Pthread implementations in turn make heavy use of operating system primitives with complicated low-level specifications (e.g., Linux's \lstinline{futex}). In this section, we instead verify simple and portable implementations of the mutexes that synchronize using atomic operations instead of OS primitives. We first describe the ghost state construction that underlies the $\mathsf{locked}$ and $\mathsf{not\_locked}$ predicates for each mutex, and then show that our simple implementation of each mutex meets the specifications of the previous section. -\subsection{The User(?) Library} +\subsection{The Mutex Name Library} \label{login} -\newcommand{\mutexperm}[2]{\ensuremath{\mathsf{mutex\_perm}_{#1}(#2)}} - All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{m\_locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. In order to acquire a non-recursive mutex, a thread needs to prove that it does not own that mutex: in separation-logic terms, it needs to own a resource demonstrating that it is not the current holder of the mutex. This means that each thread must own, or be able to create, a not-the-owner resource (call it a ``mutex permission'') %name? @@ -538,7 +531,7 @@ \subsection{The User(?) Library} \subsection{Mutex Implementations} \subsubsection{\lstinline{std::mutex}} -\newcommand{\token}[1]{\ensuremath{\ownGhost{#1}{()}}} +\renewcommand{\token}[1]{\ensuremath{\ownGhost{#1}{()}}} \begin{figure}[htb] \begin{lstlisting} diff --git a/papers/cpp-mutex/stdcpp.tex b/papers/cpp-mutex/stdcpp.tex index 135dbd4f..d959f2b0 100644 --- a/papers/cpp-mutex/stdcpp.tex +++ b/papers/cpp-mutex/stdcpp.tex @@ -419,11 +419,11 @@ \subsection{\lstinline{unique_lock}} %how much of this detail is useful? There are a lot of variations. \[\begin{array}{c} -\{p \mapsto R(q, m) \ast \dolock{p}{m}{K}\}\\ \texttt{unique\_lock(p)}\\ \{\ulock(\mathsf{Some}\ (\mathsf{false}, p, q, m))\} \vspace{1em}\\%constructor notation? -\{u \mapsto \ulock(m)\ast \neg \mathsf{held}(m) \ast \dolock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {u\texttt{.lock()}} \\ \{m \mapsto \ulock(m[\mathsf{held} := \mathsf{true}]) \ast K\} \vspace{1em}\\ - \{u \mapsto \ulock(m)\ast \mathsf{held}(m) \ast \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {u\texttt{.unlock}()}\\ +\{p \mapsto R(q, m) \ast \dolock{p}{m}{K}\}\\ \texttt{unique\_lock(p)}\\ \{u.\ u \mapsto \ulock(\mathsf{Some}\ (\mathsf{false}, p, q, m))\} \vspace{1em}\\%constructor notation? +\{u \mapsto \ulock(m)\ast \neg \mathsf{held}(m) \ast \dolock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {\texttt{u.lock()}} \\ \{m \mapsto \ulock(m[\mathsf{held} := \mathsf{true}]) \ast K\} \vspace{1em}\\ + \{u \mapsto \ulock(m)\ast \mathsf{held}(m) \ast \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {\texttt{u.unlock}()}\\ \{m \mapsto \ulock(m[\mathsf{held} := \mathsf{false}]) \ast K\} \vspace{1em} \\ -\{u \mapsto \ulock() \ast (\text{if } \mathsf{held}(m) \text{ then } \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\} \text{ else } K)\}\\ \sim u()\\ \{K \ast \text{if } m \text{ then } \mathsf{ptr}(m) \mapsto R(\mathsf{q}(m), \mathsf{m}(m)) \text { else } \mathsf{emp}\} %destructor notation, Some/None notation +\{u \mapsto \ulock() \ast (\text{if } \mathsf{held}(m) \text{ then } \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\} \text{ else } K)\}\\ \texttt{u.}\!\!\sim\texttt{unique\_lock}()\\ \{K \ast \text{if } m \text{ then } \mathsf{ptr}(m) \mapsto R(\mathsf{q}(m), \mathsf{m}(m)) \text { else } \mathsf{emp}\} %destructor notation, Some/None notation \end{array}\] %verify the code from cppreference or a similar example? @@ -490,14 +490,35 @@ \section{Case Study: Mutex Encapsulation} The code of our case study is shown in Figure~\ref{transfer}. Each object of class \lstinline{Account} has a \lstinline{balance} protected by a mutex (in this code we use a \lstinline{recursive_mutex}, but any form of mutex would work). %make generic? -The \lstinline{transfer_to} method decrements the host object's %terminology? +The \lstinline{transfer_to} method decrements the receiver's %terminology? balance and increments the target object's balance, acquiring both mutexes in the process. Our questions are then: \begin{enumerate} -\item How do we prove that we can acquire \lstinline{target}'s mutex, given that we already hold the host's mutex? +\item How do we prove that we can acquire \lstinline{target}'s mutex, given that we already hold the receiver's mutex? \item Can we use a representation predicate for \lstinline{Account} that is freely transferable between threads and does not take a thread ID as a parameter? \end{enumerate} +%how much do we need to explain namespaces? + +For question 2, recall that our specifications for \lstinline{recursive_mutex} are: +\[\begin{array}{c} +\{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n}{P}{a}\}\\ {\texttt{m.lock()}} \\ \{m \mapsto_q \rmutex{\gamma}{P}\ast \exists a'.\ \mathsf{acquire}(n, a, a') \ast \acquirable{\gamma}{t}{n+1}{P}{a'}\} \vspace{1em}\\ + \{\currentthread{t} \ast m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n+1}{P}{a}\}\\ {\texttt{m.unlock}()}\\ + \{m \mapsto_q \rmutex{\gamma}{P}\ast\acquirable{\gamma}{t}{n}{P}{a}\} +\end{array}\] +The $\mathsf{holds}$ predicate takes a thread $t$ that must match the current thread, and we must have it at line 15 %confirm +in order to acquire \lstinline{mut}. On the other hand, \lstinline{transfer_to} is a function that can be called by any thread that has access to the two accounts involved, and should have a specification like +\[ \{ a \mapsto_q \mathsf{account}{\gamma_a} \ast b \mapsto_q \mathsf{account}{\gamma_b} \}\ \texttt{a.transfer\_to(b, n)}\ \{ a \mapsto_q \mathsf{account}{\gamma_a} \ast b \mapsto_q \mathsf{account}{\gamma_b} \}\] +In particular, if $\mathsf{account}$ takes a thread $t$ as an argument, then we will not be able to freely transfer ownership of it between threads (since it would only be usable by that thread $t$). The mutex permission library allows us to isolate this thread-local information. + +If the precondition of \lstinline{transfer_to} includes $\mathsf{holds}$, then it will also need to mention the thread $t$, and so will not be freely + +The representation predicate for \lstinline{Account} is defined as +\[p \mapsto_q \mathsf{account}(\gamma) \triangleq p.\texttt{mut} \mapsto \rmutex{\gamma}{\lambda n.\ p.\texttt{balance} \mapsto n} \ast \mathsf{token}_\gamma(q) +\] +Ownership of an \lstinline{Account} is simply ownership of the mutex protecting the \lstinline{balance} field, plus ownership of the $\mathsf{token}$ that tracks whether the lock is held. In particular, it makes no mention of the $\mathsf{holds}$ predicate that appears in the precondition of the \lstinline{recursive_mutex} specifications (and explicitly takes a thread $t$ as an argument). Then, how does + \section{BRiCk Formalization} +note that BRiCk itself is an axiomatic semantics \section{Conclusion and Future Work} ... From eafd586dd72bec904eef8537d851e08e844555dd Mon Sep 17 00:00:00 2001 From: William Mansky Date: Thu, 1 Oct 2026 16:32:33 -0500 Subject: [PATCH 068/109] small notation tweaks --- papers/cpp-mutex/specs.tex | 4 ++-- papers/cpp-mutex/stdcpp.tex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index bef5212b..1255547c 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -153,7 +153,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} unsigned int x = 0; std::mutex m; - (*@ $\{ \currentthread{t} \ast \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \token{\gamma}{q} \ast \mutexperm{t}{\mutexname{\gamma}}$ @*) + (*@ $\{ \currentthread{t} \ast \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \token{\gamma}{q} \ast \mutexperm{t}{\mutexname{\gamma}} \}$ @*) void double_incr() { (*@ $\Rightarrow \{ \currentthread{t} \ast \texttt{m} \mapsto_\pi \mutex{\gamma}{P} \ast \notlocked{\gamma}{t}{q} \}$ @*) m.lock(); @@ -170,7 +170,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1} \ast \mutexperm{t}{\top} \}$ @*) std::thread t(double_incr); (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \token{\gamma}{.5} \ast \mutexperm{t}{\top} \} \Rightarrow$ @*) - (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \mutexperm{t}{\top \setminus \{\mutexname{\gamma}\}} \ast \notlocked{\gamma}{t_1}{.5} \}$ @*) + (*@ $\{ \currentthread{t_1} \ast \texttt{m} \mapsto_{.5} \mutex{\gamma}{P} \ast \mutexperm{t}{\top \setminus \mutexname{\gamma}} \ast \notlocked{\gamma}{t_1}{.5} \}$ @*) m.lock(); (*@ $\{ ... \ast P \ast \locked{\gamma}{t_1}{.5} \}$ @*) x += 2; diff --git a/papers/cpp-mutex/stdcpp.tex b/papers/cpp-mutex/stdcpp.tex index d959f2b0..28b2cec1 100644 --- a/papers/cpp-mutex/stdcpp.tex +++ b/papers/cpp-mutex/stdcpp.tex @@ -423,7 +423,7 @@ \subsection{\lstinline{unique_lock}} \{u \mapsto \ulock(m)\ast \neg \mathsf{held}(m) \ast \dolock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {\texttt{u.lock()}} \\ \{m \mapsto \ulock(m[\mathsf{held} := \mathsf{true}]) \ast K\} \vspace{1em}\\ \{u \mapsto \ulock(m)\ast \mathsf{held}(m) \ast \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\}\\ {\texttt{u.unlock}()}\\ \{m \mapsto \ulock(m[\mathsf{held} := \mathsf{false}]) \ast K\} \vspace{1em} \\ -\{u \mapsto \ulock() \ast (\text{if } \mathsf{held}(m) \text{ then } \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\} \text{ else } K)\}\\ \texttt{u.}\!\!\sim\texttt{unique\_lock}()\\ \{K \ast \text{if } m \text{ then } \mathsf{ptr}(m) \mapsto R(\mathsf{q}(m), \mathsf{m}(m)) \text { else } \mathsf{emp}\} %destructor notation, Some/None notation +\{u \mapsto \ulock() \ast (\text{if } \mathsf{held}(m) \text{ then } \dounlock{\mathsf{ptr}(m)}{\mathsf{m}(m)}{K}\} \text{ else } K)\}\\ \texttt{u.}\!\!\sim\!\texttt{unique\_lock}()\\ \{K \ast \text{if } m \text{ then } \mathsf{ptr}(m) \mapsto R(\mathsf{q}(m), \mathsf{m}(m)) \text { else } \mathsf{emp}\} %destructor notation, Some/None notation \end{array}\] %verify the code from cppreference or a similar example? From 5fb7540a5bac05677e91264f84077fbcc0888973 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Sun, 4 Oct 2026 23:13:41 -0500 Subject: [PATCH 069/109] add some comments to paper --- papers/cpp-mutex/specs.tex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index 1255547c..f0d1a66e 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -145,7 +145,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} \caption{An example using \lstinline{std::mutex}} \label{mutex-ex} \end{figure} -Figure~\ref{mutex-ex} shows a simple application of \lstinline{mutex}. The mutex \lstinline{m} protects the variable \lstinline{x}, which is incremented by two separate threads. We sketch a proof with the lock invariant $P \triangleq \exists n.\ \texttt{x} \mapsto n \ast \mathsf{even}(n)$; we could use ghost state to show that \lstinline{x} is exactly 4 at the end of the program, but this is orthogonal to the novelties of our mutex specification. The proof is outlined in Figure~\ref{mutex-ex-proof}. When the main thread spawns a new thread executing \lstinline{double_incr}, it gives the new thread partial ownership of the mutex and its token; it does not need to give up $\currentthread{t_1}$ or $\mutexperm{t}{\top}$, because the new thread automatically receives its own thread-specific resources. Before calling \lstinline{m.lock}, each thread registers with the mutex, exchanging $\mutexperm{}{\mutexname{\gamma}}$ and a piece of the token for a $\mathsf{not\_locked}$ predicate. When a thread locks \lstinline{m} it exchanges its $\mathsf{not\_locked}$ for $\mathsf{m\_locked}$ and ownership of $P$; unlocking \lstinline{m} performs the same operation in reverse. After each thread finishes using \lstinline{m}, it unregisters and recovers its token and mutex permission. +Figure~\ref{mutex-ex} shows a simple application of \lstinline{mutex}. The mutex \lstinline{m} protects the variable \lstinline{x}, which is incremented by two separate threads. We sketch a proof with the lock invariant $P \triangleq \exists n.\ \texttt{x} \mapsto n \ast \mathsf{even}(n)$; we could use ghost state to show that \lstinline{x} is exactly 4 at the end of the program, but this is orthogonal to the novelties of our mutex specification. The proof is outlined in Figure~\ref{mutex-ex-proof}. When the main thread spawns a new thread executing \lstinline{double_incr}, it gives the new thread partial ownership of the mutex and its token; it does not need to give up $\currentthread{t_1}$ or $\mutexperm{t}{\top}$\kedu{this sentence is a bit strange to me, but I don't have a better alternative right now}, because the new thread automatically receives its own thread-specific resources at spawn time. Before calling \lstinline{m.lock}, each thread registers with the mutex, exchanging $\mutexperm{}{\mutexname{\gamma}}$ and a piece of the token for a $\mathsf{not\_locked}$ predicate. When a thread locks \lstinline{m} it exchanges its $\mathsf{not\_locked}$ for $\mathsf{m\_locked}$ and ownership of $P$; unlocking \lstinline{m} performs the same operation in reverse. After each thread finishes using \lstinline{m}, it unregisters and recovers its token and mutex permission. Finally, the main thread joins with the spawned thread and recollects its shares of the mutex and token; the full $\token{\gamma}{1}$ guarantees that no thread holds the lock, satisfying the precondition of \lstinline{m}'s destructor. \begin{figure}[htb] @@ -182,7 +182,7 @@ \subsection{\texorpdfstring{\lstinline{std::mutex}}{std::mutex}} (*@ $\{ \texttt{m} \mapsto \mutex{\gamma}{P} \ast \token{\gamma}{1} \ast \mutexperm{t}{\top} \}$ @*) } \end{lstlisting} -\caption{The proof outline for the example} +\caption{The proof outline for the example\kedu{can we use t0 for the main thread, t1 for the spawned thread, and t for a general thread (used in double\_incr)?}} \label{mutex-ex-proof} \end{figure} @@ -504,7 +504,7 @@ \subsection{The Mutex Name Library} All three of our mutex specifications use thread-specific predicates ($\mathsf{not\_locked}$, $\mathsf{m\_locked}$, $\mathsf{holds}$, etc.) to enforce the mutexes' requirements on thread behavior. Here we describe a general ghost construction that we use to implement all of our thread-specific predicates. -In order to acquire a non-recursive mutex, a thread needs to prove that it does not own that mutex: in separation-logic terms, it needs to own a resource demonstrating that it is not the current holder of the mutex. This means that each thread must own, or be able to create, a not-the-owner resource (call it a ``mutex permission'') %name? +In order to acquire a non-recursive mutex, a thread needs to prove that it does not own that mutex: in separation-logic terms, it needs to own a resource demonstrating that it is not the current holder of the mutex. This means that each thread must own, or be able to create, a not-the-owner resource (call it a ``mutex permission'' \kedu{"mutex deposit"?}) %name? for every mutex it does not currently hold. We model this using disjoint infinite sets as ghost state: each thread $t$ is created with a resource $\mutexperm{t}{\top}$, where $\top$ is the universe of possible mutex names, and $\ownGhost{}{\mutexperm{t}{A \uplus B}} \dashv\vdash \ownGhost{}{\mutexperm{t}{A}} \ast \ownGhost{}{\mutexperm{t}{B}}$ (where $A \uplus B$ is only defined if $A$ and $B$ are disjoint). Then we can implement $\notlocked{\gamma}{t}$ as precisely $\ownGhost{}{\mutexperm{t}{\{\gamma\}}}$. %update with token ownership When a thread wants to interact with a mutex $\gamma$, it detaches $\mutexperm{t}{\{\gamma\}}$ from its set and uses it as permission to acquire $\gamma$; disjointness guarantees that each thread can only have one such permission. %contrast with the less-good user library approach? From 3463b74648a615f94087e086b6365151e37425ab Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Sun, 4 Oct 2026 23:19:29 -0500 Subject: [PATCH 070/109] add comment to paper --- papers/cpp-mutex/specs.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/cpp-mutex/specs.tex b/papers/cpp-mutex/specs.tex index f0d1a66e..6102bc6c 100644 --- a/papers/cpp-mutex/specs.tex +++ b/papers/cpp-mutex/specs.tex @@ -497,7 +497,7 @@ \subsection{\texorpdfstring{\lstinline{std::shared_mutex}}{std::shared\_mutex}} } \section{Implementing the Mutex Specifications} -Our specifications of C++ mutexes capture the informal specifications of the mutexes and can be used to verify client programs. Ideally, we would next show that standard library implementations actually meet our specifications. However, the widely used implementations of the C++ standard library would be difficult to verify---they use Pthread implementations of the corresponding mutex types, which have nearly the same specifications as the standard library mutexes, and the Pthread implementations in turn make heavy use of operating system primitives with complicated low-level specifications (e.g., Linux's \lstinline{futex}). In this section, we instead verify simple and portable implementations of the mutexes that synchronize using atomic operations instead of OS primitives. We first describe the ghost state construction that underlies the $\mathsf{locked}$ and $\mathsf{not\_locked}$ predicates for each mutex, and then show that our simple implementation of each mutex meets the specifications of the previous section. +Our specifications of C++ mutexes capture the informal specifications of the mutexes and can be used to verify client programs. Ideally, we would next show that standard library implementations actually meet our specifications. However, the widely used implementations of the C++ standard library would be difficult to verify---they use Pthread implementations of the corresponding mutex types, which have nearly the same specifications as the standard library mutexes, and the Pthread implementations in turn make heavy use of operating system primitives with complicated low-level specifications (e.g., Linux's \lstinline{futex}). In this section, we instead verify simple and portable implementations of the mutexes that synchronize using atomic operations instead of OS primitives.\kedu{Do we want to explain why we think they are functionally equivalent?} We first describe the ghost state construction that underlies the $\mathsf{locked}$ and $\mathsf{not\_locked}$ predicates for each mutex, and then show that our simple implementation of each mutex meets the specifications of the previous section. \subsection{The Mutex Name Library} \label{login} From 42fb5c2fb2eb4a4821517113823a8392a7ac5e80 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 5 Oct 2026 21:51:26 +0000 Subject: [PATCH 071/109] refactor recursive_mutex predicate interface, now similar to mutex_pred locked takes a [this] pointer, since its implementation may need it --- .../proof/mutex/spec/recursive_mutex.v | 244 ++++++++++-------- .../proof/mutex/spec/recursive_mutex_atomic.v | 84 +++--- .../mutex/custom_recursive_mutex_hpp_proof.v | 2 +- .../mutex/transfer_with_mutex_cpp_proof.v | 42 +-- 4 files changed, 204 insertions(+), 168 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 15c4993e..c352cb9a 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -16,9 +16,10 @@ Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Import linearity. (** * This file contains: - - The logical states ([RecursiveMutexState]) - - An interface of predicates [RECURSIVE_MUTEX_PREDS] - - Recursive mutex specs [recursive_mutex_spec] + - The logical states [RecursiveMutexState] + - The predicate interface [RECURSIVE_MUTEX_PREDS] + - Recursive mutex specs that are parametric to their C++ names + [recursive_mutex_spec] - And specs that are bound to stdlib names [StdRecursiveMutex] *) (** The C++ class a recursive-mutex specification is stated at. *) @@ -108,116 +109,93 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - Parameter given_token : forall `{Σ : cpp_logic, !G Σ}, - gname -> cQp.t -> thread_idT -> nat -> mpred. - - Definition acquireable `{Σ : cpp_logic, !G Σ} - {HAS_THREADS : HasStdThreads Σ} (g : gname) (qt : cQp.t) (th : thread_idT) - {TT : tele} (s : acquire_state TT) (P : TT -t> mpred) : mpred := - current_thread th ** - match s with - | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th - (CoPset $ ↑rmutex_inv_namespace g) - | Held n xs => given_token g qt th n ** @tele_app TT (fun _ => mpred) P xs - end. + Parameter not_locked : forall `{Σ : cpp_logic, !G Σ}, + gname -> thread_idT -> coPset_disj -> mpred. + Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, + ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. + (* abstract predicate of owership of a recursive mutex that protects some resource [P], e.g. in the form of [inv _ P]. Note that it only represents the mutex data structure, not the ownership of [P]. *) Parameter R : forall `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque token given_token acquireable R : sl_opacity typeclass_instances. + #[global] Hint Opaque token not_locked locked R : sl_opacity typeclass_instances. Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. - Context {HAS_THREADS : HasStdThreads Σ}. - #[global] Declare Instance given_token_timeless g qt th n : - Timeless (given_token g qt th n). - #[global] Instance acquireable_current_thread : - `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. - Proof. rewrite /acquireable; apply _. Qed. - - (** A thread's handle to the mutex is equivalent to a fractional ownership - of the [mutex] and a fraction of [token], and its [my_mutexes] with the - same namespace. *) - Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) - {TT : tele} (P : TT -t> mpred) : - current_thread th ** token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - acquireable g qt th NotHeld P. - Proof. by rewrite /acquireable. Qed. + #[global] Declare Instance not_locked_timeless g th E : + Timeless (not_locked g th E). + + Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. + + #[global] Declare Instance locked_timeless this g qt th n : + Timeless (locked this g qt th n). - Context {σ : genv}. #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). - (** Owning any given_token (which implies [th] holds the lock) contradicts - resources needed to destruct the lock. *) - (* FIXME should the following be [acquireable Held] instead of [given_token] - and [given_token] be removed from this module? *) - Parameter locked_contradict_full_token : forall (this : ptr) g q qt th n P, - this |-> R g q P ** token g 1$m ** given_token g qt th n |-- - (|={⊤}=> False). End with_cpp. End RECURSIVE_MUTEX_PREDS. -(** Non-atomic specifications and their automation use only the public rules. *) -Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T). - Import Preds. +(** Shared contracts, [BasicLockable] presentation, and proof automation. *) +Module recursive_mutex_spec (T : MutexCPPName) + (Preds : RECURSIVE_MUTEX_PREDS T). + Import Preds RecursiveMutexState. Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. - Context {HAS_THREADS : HasStdThreads Σ}. - Context {σ : genv}. - - Definition ctor_spec : ptr -> WpSpec mpred val val := - (\this this - \pre{pool N TT P xs} |> @tele_app TT (fun _ => mpred) P xs - \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) - \post Exists g, - [| pool_name g = pool /\ rmutex_inv_namespace g = N |] ** - this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** token g 1$m). - - Definition dtor_spec : ptr -> WpSpec mpred val val := - (\this this - \pre{g TT P} this |-> R g 1 (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre token g 1$m - \post |> (Exists xs, @tele_app TT (fun _ => mpred) P xs)). + Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. + + Definition acquireable (this : ptr) (g : gname) (qt : cQp.t) + (th : thread_idT) {TT : tele} (s : acquire_state TT) + (P : TT -t> mpred) : mpred := + current_thread th ** + match s with + | NotHeld => token g qt ** not_locked g th + (CoPset $ ↑rmutex_inv_namespace g) + | Held n xs => locked this g qt th n ** @tele_app TT (fun _ => mpred) P xs + end. + #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. + + #[global] Instance acquireable_current_thread this g qt th TT s P : + Observe (current_thread th) (acquireable this g qt th (TT := TT) s P). + Proof. rewrite /acquireable. apply _. Qed. Definition lock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{qt th n} acquireable g qt th n P - \post Exists n', [| acquire n n' |] ** ▷ acquireable g qt th n' P). + \pre{qt th n} acquireable this g qt th n P + \post Exists n', [| acquire n n' |] ** ▷ acquireable this g qt th n' P). Definition unlock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{qt th n args} acquireable g qt th (Held n args) P - \post acquireable g qt th (release $ Held n args) P). + \pre{qt th n args} acquireable this g qt th (Held n args) P + \post acquireable this g qt th (release $ Held n args) P). - Definition do_lock (lk : gname * mpred) (K : mpred) : mpred := + Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ TT P qt th n, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** - acquireable lk.1 qt th n P ** + acquireable this lk.1 qt th n P ** ((* TODO readd *) (* ▷ *) - (Exists n', [| acquire n n' |] ** ▷ acquireable lk.1 qt th n' P) -* K). + (Exists n', [| acquire n n' |] ** ▷ acquireable this lk.1 qt th n' P) -* K). #[global] Arguments do_lock /. - Definition do_unlock (lk : gname * mpred) (K : mpred) : mpred := + Definition do_unlock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ TT P qt th n args, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** - acquireable lk.1 qt th (Held n args) P ** + acquireable this lk.1 qt th (Held n args) P ** ((* TODO readd *) (* ▷ *) - acquireable lk.1 qt th (release $ Held n args) P -* K). + acquireable this lk.1 qt th (release $ Held n args) P -* K). #[global] Arguments do_unlock /. #[global] Instance recursive_mutex_basic_lockable : BasicLockable (T := gname * mpred) cpp_ty (fun q gP => R gP.1 q gP.2) := - { do_lock := fun this => do_lock - ; do_unlock := fun this => do_unlock }. + { do_lock := do_lock + ; do_unlock := do_unlock }. Definition lock_spec_alt : ptr -> WpSpec mpred val val := lock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). @@ -232,7 +210,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) - iIntros "H". iDestruct "H" as (g TT P q qt th n) "(%Hxs & HR & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), - (Exists n', [| acquire n n' |] ** ▷ acquireable g qt th n' P)%I. + (Exists n', [| acquire n n' |] ** ▷ acquireable this g qt th n' P)%I. iFrame "HR HK". iSplit; first done. iExists TT, P, qt, th, n. iFrame "HA". iSplit; first done. iIntros "$". @@ -253,7 +231,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) - iIntros "H". iDestruct "H" as (g TT P q qt th n args) "(%Hxs & HR & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), - (acquireable g qt th (release $ Held n args) P). + (acquireable this g qt th (release $ Held n args) P). iFrame "HR HK". iSplit; first done. iExists TT, P, qt, th, n, args. iFrame "HA". iSplit; first done. iIntros "$". @@ -266,6 +244,20 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. + Definition ctor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{pool N TT P xs} |> @tele_app TT (fun _ => mpred) P xs + \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) + \post Exists g, + [| pool_name g = pool /\ rmutex_inv_namespace g = N |] ** + this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** token g 1$m). + + Definition dtor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{g TT P} this |-> R g 1 (∃ xs, @tele_app TT (fun _ => mpred) P xs) + \pre token g 1$m + \post |> (Exists xs, @tele_app TT (fun _ => mpred) P xs)). + Section proof_automation. #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). @@ -286,48 +278,48 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) (** The mutex representation remembers the resource after clients open it. *) #[global] Instance R_acquireable_learn_TT (this : ptr) : `{Learnable (this |-> R g q (∃ xs : tele_arg TT1, @tele_app TT1 (fun _ => mpred) P1 xs)) - (acquireable g qt th (TT := TT2) t P2) + (acquireable this g qt th (TT := TT2) t P2) [TT2 = TT1] }. Proof. solve_learnable. Qed. #[global] Instance R_acquireable_learn_P TT (this : ptr) : `{Learnable (this |-> R g q (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P1 xs)) - (acquireable g qt th (TT := TT) t P2) + (acquireable this g qt th (TT := TT) t P2) [P2 = P1] }. Proof. solve_learnable. Qed. #[global] Instance acquireable_learn_TT : `{Learnable - (acquireable g qt1 th (TT := TT1) t1 P1) - (acquireable g qt2 th (TT := TT2) t2 P2) + (acquireable this g qt1 th (TT := TT1) t1 P1) + (acquireable this g qt2 th (TT := TT2) t2 P2) [qt2 = qt1; TT2 = TT1] }. Proof. solve_learnable. Qed. - #[global] Instance acquireable_learn γ th TT : - LearnEq3 (fun qt s P => acquireable γ qt th (TT := TT) s P). + #[global] Instance acquireable_learn this γ th TT : + LearnEq3 (fun qt s P => acquireable this γ qt th (TT := TT) s P). Proof. solve_learnable. Qed. - #[global] Instance given_token_learn γ th : LearnEq2 (fun qt n => given_token γ qt th n). + #[global] Instance locked_learn this γ th : LearnEq2 (fun qt n => locked this γ qt th n). Proof. solve_learnable. Qed. - #[global] Instance later_acquireable_learn γ th TT : - LearnEq3 (fun qt a b => bi_later (acquireable γ qt th (TT := TT) a b)). + #[global] Instance later_acquireable_learn this γ th TT : + LearnEq3 (fun qt a b => bi_later (acquireable this γ qt th (TT := TT) a b)). Proof. solve_learnable. Qed. #[global] Instance : `{Learnable (current_thread th) - (acquireable (TT := TT0) γ qt th0 args P0) + (acquireable this (TT := TT0) γ qt th0 args P0) [th0 = th] }. Proof. solve_learnable. Qed. - #[global] Instance learn_args + #[global] Instance learn_args (this : ptr) {TT : tele} (t : acquire_state TT) (P : TT -t> mpred) : `{Learnable - (@tele_app TT (fun _ => mpred) P args ** given_token γ qt th n) - (acquireable γ qt' th t P) + (@tele_app TT (fun _ => mpred) P args ** locked this γ qt th n) + (acquireable this γ qt' th t P) [qt' = qt; t = Held n args] }. Proof. solve_learnable. Qed. - #[global] Instance given_token_acquireable_learn {TT : tele} : `{Learnable - (given_token g qt th n) - (acquireable (TT := TT) g qt' th (Held n' args) P) + #[global] Instance locked_acquireable_learn {TT : tele} : `{Learnable + (locked this g qt th n) + (acquireable this (TT := TT) g qt' th (Held n' args) P) [qt' = qt; n' = n] }. Proof. solve_learnable. Qed. @@ -335,13 +327,13 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) ltac:(mk_obs_fwd acquireable_current_thread). #[program] - Definition acquireable_is_acquired_C {TT} g qt th t t' P + Definition acquireable_is_acquired_C {TT} this g qt th t t' P (_ : acquire (TT := TT) t t') := \cancelx - \consuming acquireable g qt th t' P + \consuming acquireable this g qt th t' P \deduce{args} @tele_app TT (fun _ => mpred) P args \deduce{n} [| t' = Held n args /\ t = release t' |] - \deduce given_token g qt th n + \deduce locked this g qt th n \end. Next Obligation. intros * (? & ? & -> & ->)%is_held. @@ -349,12 +341,12 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Qed. #[program] - Definition acquireable_acquireable_C γ := + Definition acquireable_acquireable_C this γ := \cancelx - \consuming{qt th n TT args P} acquireable (TT := TT) γ qt th (Held n args) P + \consuming{qt th n TT args P} acquireable this (TT := TT) γ qt th (Held n args) P \bound P' \bound_existential qt' th' args' - \proving acquireable γ qt' th' args' P' + \proving acquireable this γ qt' th' args' P' \instantiate qt' := qt \instantiate th' := th \instantiate args' := Held n args @@ -364,12 +356,12 @@ Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T) Next Obligation. intros. rewrite /acquireable /=; work. Qed. #[program] - Definition own_P_is_acquireable_C {TT} g qt n P := + Definition own_P_is_acquireable_C {TT} this g qt n P := \cancelx \preserving{th} current_thread th - \consuming given_token g qt th n + \consuming locked this g qt th n \bound n' args - \proving acquireable (TT := TT) g qt th (Held n' args) P + \proving acquireable this (TT := TT) g qt th (Held n' args) P \through @tele_app TT (fun _ => mpred) P args \through [| n' = n |] \end. @@ -431,7 +423,55 @@ Module StdRecursiveMutex End with_cpp. End StdRecursiveMutex. -(* export a recursive_mutex module bound to StdRecursiveMutexName but - RECURSIVE_MUTEX_PREDS is still abstract.*) -Declare Module StdRecursiveMutexPreds : RECURSIVE_MUTEX_PREDS StdRecursiveMutexName. -Module std_recursive_mutex := StdRecursiveMutex StdRecursiveMutexPreds. +(** The standard-library boundary instantiates the same interface. Its + registration permission is a namespace handle. *) +Module StdRecursiveMutexPreds <: RECURSIVE_MUTEX_PREDS StdRecursiveMutexName. + Include RecursiveMutexState. + Definition cpp_ty : type := StdRecursiveMutexName.cpp_ty. + Parameter gname : Set. + Parameter G : forall `{Σ : cpp_logic}, Type. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + Parameter pool_name : gname -> iprop.gname. + Parameter rmutex_inv_namespace : gname -> namespace. + #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. + Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. + #[global] Declare Instance token_fractional + `{Σ : cpp_logic, !G Σ} g : CFractional (token g). + #[global] Declare Instance token_timeless + `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). + + Definition not_locked `{Σ : cpp_logic, !G Σ} + (g : gname) (th : thread_idT) (E : coPset_disj) : mpred := + MutexSets.my_mutexes (pool_name g) th E. + Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, + ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. + Parameter R : forall `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv}, + gname -> cQp.t -> mpred -> Rep. + #[global] Hint Opaque token not_locked locked R : sl_opacity typeclass_instances. + + Section with_cpp. + Context `{Σ : cpp_logic, !G Σ}. + #[global] Instance not_locked_timeless g th E : + Timeless (not_locked g th E). + Proof. rewrite /not_locked. apply _. Qed. + + Lemma register_thread g th : + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ + not_locked g th (CoPset $ ↑rmutex_inv_namespace g). + Proof. by rewrite /not_locked. Qed. + + Context `{!HasStdThreads Σ, σ : genv}. + + #[global] Declare Instance locked_timeless this g qt th n : + Timeless (locked this g qt th n). + #[only(cfractional,cfracvalid,ascfractional)] derive R. + #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). + End with_cpp. +End StdRecursiveMutexPreds. + +Module std_recursive_mutex. + Include StdRecursiveMutex StdRecursiveMutexPreds. + Definition register_thread := @StdRecursiveMutexPreds.register_thread. +End std_recursive_mutex. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index e5512c0a..f015cef0 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -374,39 +374,35 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Timeless (given_token g qt th n). Proof. rewrite /given_token. apply _. Qed. - Definition acquireable `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ} - (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (t : acquire_state TT) - (P : TT -t> mpred) : mpred := - current_thread th ** - match t with - | NotHeld => token g qt ** MutexSets.my_mutexes (pool_name g) th - (CoPset $ ↑rmutex_inv_namespace g) - | Held n args => given_token g qt th n ** tele_app P args - end. - - #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. + Definition not_locked `{Σ : cpp_logic, !G Σ} + (g : gname) (th : thread_idT) (E : coPset_disj) : mpred := + MutexSets.my_mutexes (pool_name g) th E. + Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} + (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := + given_token g qt th n. + #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} g th E : + Timeless (not_locked g th E). + Proof. rewrite /not_locked. apply _. Qed. + #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th n : + Timeless (locked this g qt th n). + Proof. rewrite /locked. apply _. Qed. + #[global] Hint Opaque not_locked locked : sl_opacity typeclass_instances. Section rules. - Context `{Σ : cpp_logic, !G Σ, !HasStdThreads Σ}. + Context `{Σ : cpp_logic, !G Σ}. - #[global] Instance acquireable_current_thread : - `{Observe (current_thread th) (acquireable g qt th (TT := TT) t P)}. - Proof. rewrite /acquireable; apply _. Qed. - - Lemma register_thread (g : gname) (qt : cQp.t) (th : thread_idT) - {TT : tele} (P : TT -t> mpred) : - current_thread th ** token g qt ** + Lemma register_thread g th : MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - acquireable g qt th NotHeld P. - Proof. by rewrite /acquireable. Qed. + not_locked g th (CoPset $ ↑rmutex_inv_namespace g). + Proof. by rewrite /not_locked. Qed. - Context {σ : genv}. + Context `{!HasStdThreads Σ} {σ : genv}. Lemma locked_contradict_full_token (this : ptr) g q qt th n P : - this |-> R g q P ** token g 1$m ** given_token g qt th n |-- + this |-> R g q P ** token g 1$m ** locked this g qt th n |-- (|={⊤}=> False). Proof. - rewrite /R /token /given_token !_at_sep !_at_pureR inv_rmutex.unlock. + rewrite /R /token /locked /given_token !_at_sep !_at_pureR inv_rmutex.unlock. iIntros "((HR & Hown & #Hinv) & Htoken & Hheld)". iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hauth & Hcase) Hown]". iDestruct (own_valid_2 with "Hauth Hheld") as %[=]%excl_auth_agree_L; subst. @@ -431,8 +427,9 @@ End RecursiveMutexPreds. (** Atomic specs derive the non-atomic specs. *) Module RecursiveMutexRefinement (T : MutexCPPName). Module Preds := RecursiveMutexPreds T. - Module Spec := recursive_mutex_spec T Preds. Import Preds. + Module Spec := recursive_mutex_spec T Preds. + Import Spec (acquireable). Section with_cpp. Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. @@ -440,19 +437,6 @@ Section with_cpp. Context `{!G Σ}. Context `{HOV : !HasOwnValid mpredI cmraR, HOU : !HasOwnUpd mpredI cmraR}. - Local Lemma acquireable_atomic g qt th {TT : tele} (s : acquire_state TT) P : - acquireable g qt th s P ⊣⊢ - current_thread th ** - match s with - | NotHeld => at_specs.locked g.(lock_gname) qt th 0 - | Held n xs => given_token g qt th n ** tele_app P xs - end. - Proof. - destruct s; rewrite /acquireable /=; last done. - rewrite at_specs.locked.unlock /= /pool_name /rmutex_inv_namespace /token. - iSplit; iIntros "(Hth & H1 & H2)"; iFrame. - Qed. - (* basically std_ctor_atomic_spec |-- std_ctor_non_atomic_spec *) Lemma at_spec_impl_na_spec_ctor this : elaborate.spec_entails_fupd @@ -511,12 +495,15 @@ Section with_cpp. work. rewrite /R !_at_sep !_at_pureR; work. iExists q, qt, (cinv_own g.(cinv_gname) q ** - (∃ t, [| acquire n t |] ∗ ▷ acquireable g qt th t P))%I. + (∃ t, [| acquire n t |] ∗ ▷ acquireable this g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock !acquireable_atomic /given_token /token. + rewrite inv_rmutex.unlock /acquireable /not_locked /locked /given_token + /token /pool_name /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". - destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]; work. + destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]. + 1: rewrite [in at_specs.locked _ _ _ 0]at_specs.locked.unlock /=. + all: work. 2: iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. all: work $usenamed=true; iApply fupd_mask_intro; first set_solver; iIntros "Hclose'"; work; iMod "Hclose'" as "_". @@ -531,12 +518,12 @@ Section with_cpp. first apply (excl_auth_update _ _ (1, th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). rewrite /acquireable /given_token /=. work. + iExists (Held 0 args). rewrite /acquireable /locked /given_token /=. work. - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; first apply (excl_auth_update _ _ (S (S n), th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). rewrite /acquireable /given_token /=. work. + iExists (Held (S n) args). rewrite /acquireable /locked /given_token /=. work. Qed. Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : @@ -550,9 +537,10 @@ Section with_cpp. work. rewrite /R !_at_sep !_at_pureR; work. iExists q, qt, (cinv_own g.(cinv_gname) q ** - acquireable g qt th (release $ Held n args) P)%I. + acquireable this g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock !acquireable_atomic /given_token /token. + rewrite inv_rmutex.unlock /acquireable /not_locked /locked /given_token + /token /pool_name /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. @@ -562,7 +550,11 @@ Section with_cpp. iMod "Hclose'" as "_". iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; first apply (excl_auth_update _ _ (n, th, qt)). - rewrite acquireable_atomic /given_token release.unlock; destruct n; iFrame "#∗". + rewrite /acquireable /not_locked /locked /given_token /token + /pool_name /rmutex_inv_namespace release.unlock; destruct n. + 1: rewrite [in at_specs.locked _ _ _ 0]at_specs.locked.unlock /=. + 1: iDestruct select (MutexSets.my_mutexes _ _ _ ** at_specs.token _ _) as "[? ?]". + all: iFrame "#∗". all: iMod ("Hclose" with "[-]") as "_"; ework $usenamed=true with br_erefl; done. Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index dfd40d9c..cf26a0a5 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -63,7 +63,7 @@ Module custom_recursive_mutex. (* ** std_recursive_mutex.token γ.(rec_gname) (* ? *) *) \\// exclusive_token γ.(excl_gname) - (* ** std_recursive_mutex.given_token γ.(rec_gname) (* ? *) *) + (* ** std_recursive_mutex.locked γ.(rec_gname) (* ? *) *) ) ** cinv lock_namespace γ.(cinv_gname) (∃ count owner, count_frag count ** diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index 449dafd9..bec36ccf 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -35,7 +35,7 @@ Section with_cpp. \this this \arg{mp} "m" (Vptr mp) \pre{g q P} mp |-> std_recursive_mutex.R g q P - \pre{K} std_recursive_mutex.do_lock (g, P) K + \pre{K} std_recursive_mutex.do_lock mp (g, P) K \post this |-> R mp g q P ** K ). @@ -43,7 +43,7 @@ Section with_cpp. as dtor_spec from source with ( \this this \pre{mp g q P} this |-> R mp g q P - \pre{K} std_recursive_mutex.do_unlock (g, P) K + \pre{K} std_recursive_mutex.do_unlock mp (g, P) K \post mp |-> std_recursive_mutex.R g q P ** K ). @@ -150,11 +150,11 @@ Section class_C. \arg{f} "f" (Vptr f) \persist type_ptr (Tnamed cl) f \prepost{g q} this |-> account_mutexR g q - \pre{K} std_recursive_mutex.do_lock + \pre{K} std_recursive_mutex.do_lock (this ,, _field "C::mut") (g, (∃ args : balance_args, @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)%I) (|={⊤}=> balance_callback cl f (this ,, _field "C::balance") - (std_recursive_mutex.do_unlock + (std_recursive_mutex.do_unlock (this ,, _field "C::mut") (g, (∃ args : balance_args, @tele_app balance_args (fun _ => mpred) (fun n : Z => this |-> account_balanceR n) args)%I) K)) \post K). @@ -221,13 +221,13 @@ Section class_C. iDestruct "HR" as (g) "HR". iEval (rewrite !_at_sep !_at_pureR) in "HR". iDestruct "HR" as "(HR & Htoken & %Hg)". - iAssert (std_recursive_mutex.acquireable (TT := balance_args) g q th + iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Htoken Hnames]" as "Hacquireable". - { rewrite -std_recursive_mutex.register_thread. + { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "#∗". } iExists f, g, q, - (std_recursive_mutex.acquireable (TT := balance_args) g q th + (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n) ** K)%I. iFrame "Hf HR". iSplit; first done. iSplitR "Hpost". @@ -257,7 +257,8 @@ Section class_C. iFrame. - iIntros "(HR & Hacquireable & HK)". iApply "Hpost". iFrame "HK". - iEval (rewrite -std_recursive_mutex.register_thread) in "Hacquireable". + iEval (rewrite /std_recursive_mutex.acquireable /= + -std_recursive_mutex.register_thread) in "Hacquireable". iDestruct "Hacquireable" as "(_ & Htoken & Hnames)". destruct Hg as [Hgpool HN]. iEval (rewrite Hgpool HN) in "Hnames". iFrame "Hnames". @@ -301,17 +302,19 @@ Section class_C. iDestruct select (MutexSets.my_mutexes _ th (coPset.CoPset (↑from_namespace))) as "Hfrom_names". iEval (rewrite -b) in "Hfrom_names". - iDestruct (bi.equiv_entails_1_1 _ _ - (std_recursive_mutex.register_thread source_g qfrom th (TT := balance_args) - (fun n : Z => this |-> account_balanceR n)%I) - with "[$]") as "?"; first go. + iDestruct select (std_recursive_mutex.token source_g qfrom) as "Hfrom_token". + iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th + std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I + with "[Hfrom_names Hfrom_token]" as "?". + { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. iFrame "#∗". } iDestruct select (MutexSets.my_mutexes _ th (coPset.CoPset (↑to_namespace))) as "Hto_names". iEval (rewrite -a -b0) in "Hto_names". - iDestruct (bi.equiv_entails_1_1 _ _ - (std_recursive_mutex.register_thread g qto th (TT := balance_args) - (fun n : Z => to |-> account_balanceR n)%I) - with "[$]") as "?"; first go. + iDestruct select (std_recursive_mutex.token g qto) as "Hto_token". + iAssert (std_recursive_mutex.acquireable (to ,, _field "C::mut") (TT := balance_args) g qto th + std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I + with "[Hto_names Hto_token]" as "?". + { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. iFrame "#∗". } go. rewrite std_recursive_mutex.acquire.unlock in H1, H2. destruct H1 as [[from_balance []] Hfrom]. inversion Hfrom; subst. @@ -319,15 +322,16 @@ Section class_C. go. (* Return the updated destination balance when lg_to is destroyed. *) iExists (cQp.scale (1 / 2) qto), - (std_recursive_mutex.acquireable (TT := balance_args) g qto th + (std_recursive_mutex.acquireable (to ,, _field "C::mut") (TT := balance_args) g qto th std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I. rewrite std_recursive_mutex.release.unlock /=. go. (* Return the updated source balance when lg_from is destroyed. *) iExists (cQp.scale (1 / 2) qfrom), - (std_recursive_mutex.acquireable (TT := balance_args) source_g qfrom th + (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I. rewrite std_recursive_mutex.release.unlock /=. go. - rewrite accountR.unlock /std_recursive_mutex.acquireable. go with br_erefl. + rewrite accountR.unlock /std_recursive_mutex.acquireable /= + -!std_recursive_mutex.register_thread. go with br_erefl. rewrite a b b0. go. Qed. From 37892968267019a8a9402e28af6e598a07374497 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 6 Oct 2026 06:19:14 +0000 Subject: [PATCH 072/109] fix guard_recursive_cpp_proof.v --- .../test/mutex/guard_recursive_cpp_proof.v | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index 2e197edc..f022e72a 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -1,6 +1,7 @@ (** Provisional *) Require Import skylabs.auto.cpp.prelude.proof. Require Import skylabs.brick.libstdcpp.mutex.spec. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.test.mutex.guard_recursive_cpp. Import linearity. @@ -110,19 +111,24 @@ Section with_cpp. Timeless (@tele_app TT (fun _ => mpred) (P p) args) := _. cpp.spec "test_one_answer()" from source with ( + \persist{thr} current_thread thr + \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) \post[Vint 42] emp ). cpp.spec "test_other_answer()" from source with ( + \persist{thr} current_thread thr + \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) \post[Vint 42] emp ). + (** Initialize the member lock but do not acquire it. *) cpp.spec "C::C()" from source as C_ctor_spec with ( \this this - \persist{thr} current_thread thr + \pre{pool N} emp \post Exists γ, - this |-> CR γ 1$m ** - std_recursive_mutex.used_threads γ {[thr]} ** - std_recursive_mutex.acquireable (TT := TT) γ thr std_recursive_mutex.NotHeld (P this) + [| std_recursive_mutex.pool_name γ = pool /\ + std_recursive_mutex.rmutex_inv_namespace γ = N |] ** + this |-> CR γ 1$m ** std_recursive_mutex.token γ 1$m ). (** XXX Does not appear to help *) @@ -131,25 +137,20 @@ Section with_cpp. Lemma C_ctor_ok : verify[source] "C::C()". Proof. - verify_shift; go. - iExists TT, (P this), (mk 0); go. + verify_shift. + go. + iExists pool, N, TT, (P this), (mk 0); go. rewrite (* Ugh *) -bi.later_intro. rewrite {1}P.unlock. go. - iMod (std_recursive_mutex.use_thread_acquirable (TT := TT) thr t ∅ (P this) with "[$]") as "?"; first set_solver; iModIntro. - go. - rewrite (left_id_L _ (∪)). - go. + iModIntro. go. Qed. cpp.spec "C::~C()" from source as C_dtor_spec with ( \this this - \persist{thr} current_thread thr \pre{γ} this |-> CR γ 1$m - (* \pre std_recursive_mutex.used_threads γ empty *) - \pre std_recursive_mutex.used_threads γ {[thr]} - \pre std_recursive_mutex.acquireable (TT := TT) γ thr std_recursive_mutex.NotHeld (P this) + \pre std_recursive_mutex.token γ 1$m \post emp). Lemma C_dtor_ok : @@ -157,11 +158,7 @@ Section with_cpp. std_recursive_mutex.std_dtor_spec |-- verify[source] "C::~C()". Proof. verify_shift; go. - wapply (std_recursive_mutex.logout_acquirable (TT := TT) thr _ ∅ (P this)); first by set_solver. - rewrite /= (left_id_L _ (∪)). - go with br_erefl. - iModIntro. - go. + iModIntro. go. progress destruct_tele; rewrite P.unlock /=. go. Qed. @@ -172,6 +169,18 @@ Section with_cpp. verify[source] "test_one_answer()". Proof. verify_spec; go. + iExists pool, (nroot .@@ "guard_recursive"); go. + iDestruct (MutexSets.my_mutexes_alloc_mutex_name + (std_recursive_mutex.pool_name t) thr ⊤ + (↑std_recursive_mutex.rmutex_inv_namespace t) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + wname [std_recursive_mutex.token] "Htoken". + iAssert (std_recursive_mutex.acquireable (c_addr ,, _field "C::m") + (TT := TT) t 1$m thr std_recursive_mutex.NotHeld (P c_addr))%I + with "[Htoken Hname]" as "Hacquireable". + { rewrite /std_recursive_mutex.acquireable /= + -std_recursive_mutex.register_thread. iFrame "#∗". } + iDestruct "Hacquireable" as "?". go. destruct args as [a []]. rewrite P.unlock /=. go. @@ -205,6 +214,16 @@ Section with_cpp. go. iSplitL ""; [by go | go]. rewrite P.unlock CR'.unlock; go. + match goal with g : std_recursive_mutex.gname |- _ => rename g into γ end. + iDestruct select (std_recursive_mutex.acquireable _ _ _ _ _ _) as "Hacquireable". + iEval (rewrite /std_recursive_mutex.acquireable /= + -std_recursive_mutex.register_thread) in "Hacquireable". + iDestruct "Hacquireable" as "(_ & Htoken & Hname)". + iDestruct (MutexSets.my_mutexes_join_mutex_name + (std_recursive_mutex.pool_name γ) thr ⊤ + (↑std_recursive_mutex.rmutex_inv_namespace γ) ltac:(set_solver) + with "[Hrest Hname]") as "Hnames"; first iFrame. + iFrame "Htoken". go $usenamed=true. Qed. (* TODO: when we project out equalities about Held and NotHeld, project info From 2aed90cdbd55d4df5898ba0f40eebef5cfc80a11 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 6 Oct 2026 19:51:00 +0000 Subject: [PATCH 073/109] [transfer_with_mutex_cpp_proof.v] add [my_mutexes \top] to main_spec --- .../mutex/transfer_with_mutex_cpp_proof.v | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index bec36ccf..c17a5289 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -538,19 +538,17 @@ Section clients. cpp.spec "main()" as main_spec from source with ( \persist{th} current_thread th + \prepost{gpool} MutexSets.my_mutexes gpool th (coPset.CoPset ⊤) \post[Vint 0] emp ). Lemma main_ok : verify[source] main_spec. Proof. verify_spec. - let rec allocate_pool := - first [iMod MutexSets.alloc_mutex_set_map as (gpool) "Hmap" - | progress go1; allocate_pool] in - allocate_pool. - iMod (MutexSets.mutex_sets_alloc_thread gpool ∅ th ltac:(set_solver) - with "Hmap") as "[Hmap Hnames]". - iEval (rewrite left_id_L) in "Hmap". + let rec expose_names := + first [iDestruct select (MutexSets.my_mutexes gpool th _) as "Hnames" + | progress go1; expose_names] in + expose_names. go. iExists gpool, from_namespace; go. iExists gpool, to_namespace; go. @@ -571,12 +569,7 @@ Section clients. iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th ⊤ (↑from_namespace) ltac:(set_solver) with "[$]") as "Hnames". - iDestruct (MutexSets.mutex_sets_free_thread gpool {[th]} th - ltac:(set_solver) with "[$]") as "Hmap". - iEval (rewrite difference_diag_L) in "Hmap". - iDestruct "Hmap" as "?". - iDestruct select (MutexSets.mutex_set_map _ _) as "Hmap". - iApply (affine with "Hmap"). apply mpred_BiAffine. + iFrame "Hnames". Qed. (* FIXME do we need this? *) From 5ab7bf73aa5ab2d85a4af0f0aac776abd5cf95f8 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 6 Oct 2026 19:51:38 +0000 Subject: [PATCH 074/109] [mutex/demo_cpp_proof.v] acquireable takes a this pointer --- .../proof/mutex/demo_cpp_proof.v | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v index 7730a964..a8d6d7b0 100644 --- a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v +++ b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v @@ -50,9 +50,9 @@ Section std_recursive_mutex. Context {HAS_THREADS : HasStdThreads Σ}. Context `{!std_recursive_mutex.G Σ}. - Lemma acquireable_update_equiv {TT : tele} γ qt th f t1 t2 P : + Lemma acquireable_update_equiv {TT : tele} this γ qt th f t1 t2 P : acquire t1 t2 -> - acquireable γ qt th (update f t1) P ⊣⊢ acquireable γ qt th (release (TT := TT) (update f t2)) P. + acquireable this γ qt th (update f t1) P ⊣⊢ acquireable this γ qt th (release (TT := TT) (update f t2)) P. Proof. intros. by erewrite std_recursive_mutex.update_eq. @@ -74,15 +74,15 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). cpp.spec "C::update_b(long)" as C_update_b from demo_cpp.source with (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). #[global] Instance CR_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) CR). Proof. solve_learnable. Qed. @@ -142,15 +142,15 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable γ qt th args (TT:=TT) (P this) - \post std_recursive_mutex.acquireable γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). + \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). Lemma transfer_ok : verify[source] "C::transfer(int)". Proof. verify_spec; go. destruct args as [a [b []]]; work. pose (updated := mk (trim 64 (a + x)) (trim 64 (b + (0 - x)))). - iExists (std_recursive_mutex.acquireable γ qt th + iExists (std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (std_recursive_mutex.release (std_recursive_mutex.Held n updated)) (P this)), updated, n. go. Qed. From 5b33135656f9d5ae7bf0b5bcb7e0185d8ea0427c Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 6 Oct 2026 19:52:13 +0000 Subject: [PATCH 075/109] [custom_recursive_mutex.hpp] add explicit constructor --- rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp index 0659d92f..9ff97339 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp @@ -16,6 +16,9 @@ class MyRecursiveMutex { std::mutex m_lock{}; public: + // Explicitly define the constructor so that compiler emits constructor code + MyRecursiveMutex() {} + // TODO: add the token/given_token accounting, maybe with q*tickets void lock() { From 48df3b12aa5e803026ab853b06c6518dd73b3e7c Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 6 Oct 2026 21:13:54 +0000 Subject: [PATCH 076/109] add hash functions for thread ID objects --- rocq-brick-libstdcpp/proof/thread/spec.v | 163 +++++++++++++++++++++-- 1 file changed, 151 insertions(+), 12 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index 24f9842d..3324bb90 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -4,6 +4,8 @@ Require Import skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.thread.thread_hpp. +Import linearity. + (** Specs for thread creation and joining. *) Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. @@ -42,6 +44,8 @@ Section with_cpp. ObjectiveWith threadTI (wp_fptr (σ.(genv_tu).(types)) entry_type f [] (fun _ => Q)). + (* TODO should have a generic thread spawn spec that one can pass any resrouce + to, and does not mention my_mutex. *) (** Passing a function lvalue binds a function reference directly, without an intermediate function-pointer object. The child receives its mutex namespace pool before running the entry point. Resources that the entry @@ -89,24 +93,43 @@ Section with_cpp. End with_cpp. (** Specifications for thread IDs and the current thread's operations. *) -Section thread_id_specs. - Parameter thread_idR : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> - (* None if value is thread::id(), Some otherwise *) - option thread_idT -> Rep. - #[only(cfracsplittable, type_ptr="std::thread::id")] derive thread_idR. +Module thread_id_specs. + (* Assume thread_idT can project an integer. *) + Parameter thread_id_to_Z : thread_idT -> Z. + (* Underlying integer of a thread ID object is unique. *) + Axiom thread_id_to_Z_injective : + forall x y, thread_id_to_Z x = thread_id_to_Z y -> x = y. + (* Value of thread::id(). Does not represent any thread. *) + Parameter default_thread_id : thread_idT. + + (* Assume some injective hash function. *) + Parameter thread_id_hash : thread_idT -> Z. + Axiom thread_id_hash_injective : + forall x y, thread_id_hash x = thread_id_hash y -> x = y. + + (* we know there is only one field in the implementation; we expose it here. + FIXME or should we abstract it away? *) + sl.lock + Definition thread_idR `{Σ : cpp_logic, σ : genv} (q : cQp.t) + (tid : thread_idT) : Rep := + structR "std::thread::id" q ** + _field "std::thread::id::_M_thread" |-> ulongR q (thread_id_to_Z tid). + #[only(type_ptr,cfractional,ascfractional,timeless)] derive thread_idR. + + (** Weak objectivity of thread-ID storage, as required by invariant clients. *) #[global] Axiom thread_idR_WeaklyObjective : - ∀ `{Σ : cpp_logic, σ : genv} (q : cQp.t) - (o : option thread_idT) (p : ptr), - WeaklyObjective (thread_idR q o p). + ∀ `{Σ : cpp_logic, σ : genv} (q : cQp.t) (tid : thread_idT) (p : ptr), + WeaklyObjective (thread_idR q tid p). #[global] Existing Instance thread_idR_WeaklyObjective. - Context `{Σ : cpp_logic} {σ : genv}. +Section with_cpp. + Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. cpp.spec (default_ctor "std::thread::id") as thread_id_ctor_spec from source with ( \this this - \post this |-> thread_idR 1$m None). + \post this |-> thread_idR 1$m default_thread_id). cpp.spec (const_copy_ctor "std::thread::id") as thread_id_copy_ctor_spec from source with ( @@ -144,11 +167,127 @@ Section thread_id_specs. \prepost{q2 o2} rhs |-> thread_idR q2 o2 \post[Vbool (bool_decide (o1 = o2))] emp). + (** [thread.thread.this] guarantees a running thread has a non-default ID. *) cpp.spec "std::this_thread::get_id()" as get_id_spec from source with ( - \persist{thr} current_thread thr + \persist{th} current_thread th \post{result}[Vptr result] - result |-> thread_idR 1$m (Some thr)). + result |-> thread_idR 1$m th ** [| th <> default_thread_id |]). + + #[global] Instance thread_idR_learn : + Cbn (Learn (any ==> learn_eq ==> learn_hints.fin) thread_idR). + Proof. solve_learnable. Qed. + + Definition thread_id_layout := Eval vm_compute in types source !! "std::thread::id"%cpp_name. + + #[program] + Definition thread_id_const_C (tu : translation_unit) (p : ptr) := + \cancelx + \consuming{owner from} p |-> thread_idR from owner + \guard (types tu !! "std::thread::id"%cpp_name =[Vm?]=> thread_id_layout) + \proving{to Q} wp_const tu from to p "std::thread::id" Q + \through (p |-> thread_idR to owner -* Q) + \end@{mpredI}. + Next Obligation. + intros. rewrite thread_idR.unlock !_at_sep. + iIntros "[Hstruct Hfield]" (to Q) "HQ". + iApply (wp_const_named_struct from to _ _ _ _ G with "Hstruct"). + cbn. work $usenamed=true with br_erefl. + Qed. + + (** The callable returns the thread-ID hash. Its implementation is verified + against the byte-hash primitive below. *) + cpp.spec "std::hash::operator()(const std::thread::id&) const" + as hash_spec from source with ( + \this this + \arg{value} "__id" (Vptr value) + \persist type_ptr "std::hash" this + \prepost{q owner} value |-> thread_idR q owner + \post[Vint (thread_id_hash owner)] emp). + + (** Hash-object destruction, including its empty base object. *) + cpp.spec "std::__hash_base::~__hash_base()" + as hash_base_dtor_spec from source with ( + \this this + \pre this |-> structR "std::__hash_base" 1$m + \post emp). + + cpp.spec "std::hash::~hash()" + as hash_dtor_spec from source with ( + \this this + \pre this |-> structR "std::hash" 1$m + \pre this ,, _base "std::hash" + "std::__hash_base" |-> + structR "std::__hash_base" 1$m + \post emp). cpp.spec "std::this_thread::yield()" as yield_spec from source with ( \post emp). +End with_cpp. +#[global] Hint Resolve thread_id_const_C : sl_opacity. + +(** NOTE that the hash specs and proofs are all generated by AI. + [hash_bytes_spec] cannot be verified because the spec for + <<__builtin_memcpy>> is missing. Maybe cleaner to just axiomatize + [hash_ulong_spec] and remove the rest. *) +Section hash_proofs. + Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ}. + + cpp.spec "std::_Hash_bytes(const void*, unsigned long, unsigned long)" + as hash_bytes_spec from source with ( + \arg{value} "__ptr" (Vptr value) + \arg "__len" (Vint 8) + \arg "__seed" (Vint 3339675911) + \prepost{q owner} value |-> ulongR q (thread_id_to_Z owner) + \post[Vint (thread_id_hash owner)] emp). + + cpp.spec "std::_Hash_impl::hash(const void*, unsigned long, unsigned long)" + as hash_impl_spec from source with ( + \arg{value} "__ptr" (Vptr value) + \arg "__clength" (Vint 8) + \arg "__seed" (Vint 3339675911) + \prepost{q owner} value |-> ulongR q (thread_id_to_Z owner) + \post[Vint (thread_id_hash owner)] emp). + + cpp.spec "std::_Hash_impl::hash(const unsigned long&)" + as hash_ulong_spec from source with ( + \arg{value} "__val" (Vptr value) + \prepost{q owner} value |-> ulongR q (thread_id_to_Z owner) + \post[Vint (thread_id_hash owner)] emp). + + Section with_hash_module. + Context `{MOD : source ⊧ σ}. + + Lemma hash_impl_ok : verify[source] hash_impl_spec. + Proof using MOD. verify_spec; go. iExists owner. go. Qed. + + Lemma hash_ulong_ok : verify[source] hash_ulong_spec. + Proof using MOD. verify_spec; go. iExists owner. go. Qed. + + Lemma hash_ok : verify[source] hash_spec. + Proof using MOD. + verify_spec. rewrite thread_idR.unlock. go. iExists owner. go. + rewrite thread_idR.unlock. go. + Qed. + + Lemma hash_base_dtor_ok : verify[source] hash_base_dtor_spec. + Proof using MOD. verify_spec; go. Qed. + + Lemma hash_dtor_ok : hash_base_dtor_spec |-- + verify[source] hash_dtor_spec. + Proof using MOD. verify_shift; go. iModIntro. go. Qed. + + Lemma hash_link : + denoteModule source ** hash_bytes_spec |-- + hash_spec ** hash_base_dtor_spec ** hash_dtor_spec. + Proof using MOD. + work. + wapply hash_ok. + wapply hash_ulong_ok. + wapply hash_impl_ok. + wapply hash_dtor_ok. + wapply hash_base_dtor_ok. + work $usenamed=true. + Qed. + End with_hash_module. +End hash_proofs. End thread_id_specs. From 56c9d8bc63cbf0f17886a30e1e0f4a6301d2b9e8 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 8 Oct 2026 05:49:44 +0000 Subject: [PATCH 077/109] custom_recursive_mutex_hpp_proof.v looks good; fix MUTEX_PREDS module so that [mutex_inv_namespace] is not fixed but a projection of [gname] --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 43 +- .../proof/mutex/spec/recursive_mutex.v | 76 +- .../proof/mutex/spec/recursive_mutex_atomic.v | 20 +- .../test/mutex/custom_mutex_hpp_proof.v | 111 ++- .../mutex/custom_recursive_mutex_hpp_proof.v | 727 ++++++++++++++++-- .../test/mutex/double_incr_cpp_proof.v | 13 +- 6 files changed, 794 insertions(+), 196 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index dcd8c481..60ebbf95 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -22,8 +22,7 @@ Import linearity. Module Type MUTEX_PREDS. Parameter gname : Set. Parameter cpp_ty : type. - (** Mutex-set pool agreed before mutex creation. *) - Parameter mutex_inv_namespace : namespace. + Parameter mutex_inv_namespace : gname -> namespace. Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. @@ -57,6 +56,10 @@ Module Type MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). + #[global] Declare Instance locked_WeaklyObjective + `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + WeaklyObjective (locked this γ th q). + (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) Parameter pool_name : gname -> iprop.gname. @@ -109,29 +112,32 @@ Module Type MUTEX_PREDS. Lockable (T:=gname * mpred) cpp_ty (λ q γP, R γP.1 q γP.2) := { do_try_lock := do_try_lock }. - (** Install [P] in an initialized mutex using its full representation and - token. The physical mutex remains initialized and unlocked. + (** Can switch the protected resource from [emp] to [P]. + FIXME maybe it should be any [P'] to [P]? Initializing [this |-> R old 1$m emp] probably depends on the mutex implementation. *) Axiom init_R : forall (this : ptr) (old : gname) - (pool : iprop.gname) (P : mpred), + (pool : iprop.gname) (N : namespace) (P : mpred), WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- (|={⊤}=> ∃ g, - [| pool_name g = pool |] ** + [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). - (** A thread's handle to the mutex is equivalent to a fractional ownership - of the [mutex] and a fraction of [token], and its [my_mutexes] with the - same namespace. *) + (** FIXME maybe not_locked should just be a definition. *) Axiom register_thread : forall - (this : ptr) (g : gname) (q : cQp.t) (P : mpred) - (th : thread_idT) (qt : cQp.t), - this |-> R g q P ** token g qt ** - MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace) ⊣⊢ - this |-> R g q P ** not_locked this g th qt. + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t), + token g qt ** MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace g) ⊣⊢ + not_locked this g th qt. + + (** FIXME do we really need this? *) + Axiom locked_my_mutexes_exclusive : forall + `{Σ : cpp_logic, !G Σ} {σ : genv} this g th qt, + locked this g th qt ** + MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. End with_cpp. End MUTEX_PREDS. @@ -147,6 +153,13 @@ Section with_cpp. Context {HAS_THREADS : HasStdThreads Σ}. + Lemma locked_not_locked_exclusive (this : ptr) g th qt qt' : + locked this g th qt ** not_locked this g th qt' |-- False. + Proof. + rewrite -register_thread. iIntros "[HL [_ Hnames]]". + iApply (locked_my_mutexes_exclusive with "[$HL $Hnames]"). + Qed. + (** The guarded predicate must be weakly objective for invariant allocation, which R likely has. *) Definition ctor_spec (this : ptr) : WpSpec mpred val val := diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index c352cb9a..0b2ebbde 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -109,8 +109,8 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - Parameter not_locked : forall `{Σ : cpp_logic, !G Σ}, - gname -> thread_idT -> coPset_disj -> mpred. + Parameter not_locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, + ptr -> gname -> cQp.t -> thread_idT -> mpred. Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. @@ -125,8 +125,8 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. - #[global] Declare Instance not_locked_timeless g th E : - Timeless (not_locked g th E). + #[global] Declare Instance not_locked_timeless {σ : genv} this g qt th : + Timeless (not_locked this g qt th). Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. @@ -136,6 +136,13 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). + (** FIXME maybe not_locked should just be a definition in the spec section? *) + Parameter register_thread : forall + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t), + token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ + not_locked this g qt th. + End with_cpp. End RECURSIVE_MUTEX_PREDS. @@ -152,8 +159,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (P : TT -t> mpred) : mpred := current_thread th ** match s with - | NotHeld => token g qt ** not_locked g th - (CoPset $ ↑rmutex_inv_namespace g) + | NotHeld => not_locked this g qt th | Held n xs => locked this g qt th n ** @tele_app TT (fun _ => mpred) P xs end. #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. @@ -297,6 +303,8 @@ Module recursive_mutex_spec (T : MutexCPPName) #[global] Instance acquireable_learn this γ th TT : LearnEq3 (fun qt s P => acquireable this γ qt th (TT := TT) s P). Proof. solve_learnable. Qed. + #[global] Instance not_locked_learn this γ th : LearnEq1 (fun qt => not_locked this γ qt th). + Proof. solve_learnable. Qed. #[global] Instance locked_learn this γ th : LearnEq2 (fun qt n => locked this γ qt th n). Proof. solve_learnable. Qed. #[global] Instance later_acquireable_learn this γ th TT : @@ -423,55 +431,7 @@ Module StdRecursiveMutex End with_cpp. End StdRecursiveMutex. -(** The standard-library boundary instantiates the same interface. Its - registration permission is a namespace handle. *) -Module StdRecursiveMutexPreds <: RECURSIVE_MUTEX_PREDS StdRecursiveMutexName. - Include RecursiveMutexState. - Definition cpp_ty : type := StdRecursiveMutexName.cpp_ty. - Parameter gname : Set. - Parameter G : forall `{Σ : cpp_logic}, Type. - Existing Class G. - #[global] Arguments G {_ _} Σ : assert. - Parameter pool_name : gname -> iprop.gname. - Parameter rmutex_inv_namespace : gname -> namespace. - #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. - Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. - #[global] Declare Instance token_fractional - `{Σ : cpp_logic, !G Σ} g : CFractional (token g). - #[global] Declare Instance token_timeless - `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - - Definition not_locked `{Σ : cpp_logic, !G Σ} - (g : gname) (th : thread_idT) (E : coPset_disj) : mpred := - MutexSets.my_mutexes (pool_name g) th E. - Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, - ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. - Parameter R : forall `{Σ : cpp_logic, !G Σ} - {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - gname -> cQp.t -> mpred -> Rep. - #[global] Hint Opaque token not_locked locked R : sl_opacity typeclass_instances. - - Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. - #[global] Instance not_locked_timeless g th E : - Timeless (not_locked g th E). - Proof. rewrite /not_locked. apply _. Qed. - - Lemma register_thread g th : - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - not_locked g th (CoPset $ ↑rmutex_inv_namespace g). - Proof. by rewrite /not_locked. Qed. - - Context `{!HasStdThreads Σ, σ : genv}. - - #[global] Declare Instance locked_timeless this g qt th n : - Timeless (locked this g qt th n). - #[only(cfractional,cfracvalid,ascfractional)] derive R. - #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). - End with_cpp. -End StdRecursiveMutexPreds. - -Module std_recursive_mutex. - Include StdRecursiveMutex StdRecursiveMutexPreds. - Definition register_thread := @StdRecursiveMutexPreds.register_thread. -End std_recursive_mutex. +(** The standard-library implementation remains abstract; concrete recursive + mutex implementations supply their own [RECURSIVE_MUTEX_PREDS]. *) +Declare Module RecursiveMutexPreds : RECURSIVE_MUTEX_PREDS StdRecursiveMutexName. +Module std_recursive_mutex := StdRecursiveMutex RecursiveMutexPreds. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index f015cef0..ed15c2d3 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -374,14 +374,15 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Timeless (given_token g qt th n). Proof. rewrite /given_token. apply _. Qed. - Definition not_locked `{Σ : cpp_logic, !G Σ} - (g : gname) (th : thread_idT) (E : coPset_disj) : mpred := - MutexSets.my_mutexes (pool_name g) th E. + Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := + token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := given_token g qt th n. - #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} g th E : - Timeless (not_locked g th E). + #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th : + Timeless (not_locked this g qt th). Proof. rewrite /not_locked. apply _. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th n : Timeless (locked this g qt th n). @@ -391,13 +392,14 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Section rules. Context `{Σ : cpp_logic, !G Σ}. - Lemma register_thread g th : + Context `{!HasStdThreads Σ} {σ : genv}. + + Lemma register_thread (this : ptr) g th qt : + token g qt ** MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - not_locked g th (CoPset $ ↑rmutex_inv_namespace g). + not_locked this g qt th. Proof. by rewrite /not_locked. Qed. - Context `{!HasStdThreads Σ} {σ : genv}. - Lemma locked_contradict_full_token (this : ptr) g q qt th n P : this |-> R g q P ** token g 1$m ** locked this g qt th n |-- (|={⊤}=> False). diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 3af58d1b..8bd382b4 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -25,6 +25,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. pool_gname : iprop.gname; token_gname : iprop.gname; owner_gname : iprop.gname; + state_namespace : namespace; }. Definition class_name : name := "MyMutex"%cpp_name. @@ -37,8 +38,11 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition pool_name (γ : gname) : iprop.gname := γ.(lock_state_gname).(pool_gname). - Definition mutex_inv_namespace : namespace := - nroot .@@ "MyMutex" .@ "inv_namespace". + Definition mutex_inv_namespace (g : gname) : namespace := + g.(state_namespace). + + Definition held_namespace (g : state_gname) : namespace := + g.(state_namespace) .@ "held". Class stateG `{Σ : cpp_logic} := { #[global] sets_G :: MutexSets.G Σ; @@ -56,13 +60,14 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition not_locked_ghost `{Σ : cpp_logic, !G Σ} (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := - MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** + MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑γ.(state_namespace)) ** MutexTokens.token γ.(token_gname) q. Definition owner_token `{Σ : cpp_logic, !G Σ} (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := MutexTokens.given_token γ.(token_gname) q ** - OwnerTid.owner_tid_frag γ.(owner_gname) (Some th). + OwnerTid.owner_tid_frag γ.(owner_gname) (Some th) ** + MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑held_namespace γ). #[global] Instance state_token_fractional `{Σ : cpp_logic, !G Σ} γ : CFractional (state_token γ). @@ -83,17 +88,20 @@ Module CustomMutexPreds <: MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} γ : Exclusive2 (owner_token γ). Proof. intros th1 th2 q1 q2. rewrite /owner_token. - apply observe_2_sep_r. apply _. + iIntros "(_ & F1 & _) (_ & F2 & _)". + iDestruct (OwnerTid.owner_tid_frag_exclusive with "F1 F2") as %[]. Qed. - (** While held, the invariant owns this thread's singleton mutex set - and the token balance. The thread retains its remaining mutex names. - When free, both halves of the previous owner remain in the invariant. *) + (** While held, the namespace permission is split between the invariant + and [owner_token]. The held part makes a second registration by the + same thread contradictory without opening this invariant. Unlock + rejoins the parts. While free, both owner fragments are in the invariant. *) Definition state `{Σ : cpp_logic, !G Σ} (γ : state_gname) (b : bool) : mpred := (if b then ∃ th, OwnerTid.owner_tid_auth γ.(owner_gname) (Some th) ** - MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑mutex_inv_namespace) ** + MutexSets.my_mutexes γ.(pool_gname) th + (coPset.CoPset $ ↑γ.(state_namespace) ∖ ↑held_namespace γ) ** MutexTokens.token_not_full γ.(token_gname) else ∃ owner, OwnerTid.owner_tid_auth γ.(owner_gname) owner ** @@ -108,13 +116,13 @@ Module CustomMutexPreds <: MUTEX_PREDS. Section state_laws. Context `{Σ : cpp_logic, !G Σ}. - Lemma alloc_state (γpool : iprop.gname) : - ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool |] ** + Lemma alloc_state (γpool : iprop.gname) (N : namespace) : + ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool /\ γ.(state_namespace) = N |] ** state_token γ 1$m ** state γ false. Proof. iMod MutexTokens.alloc as (gt) "[T GT]". iMod (OwnerTid.alloc None) as (go) "[OA OF]". - iModIntro. iExists (MkStateGname γpool gt go). + iModIntro. iExists (MkStateGname γpool gt go N). iSplit; first done. rewrite /state_token /state /=. iFrame "T". iExists None. iFrame "OA OF". @@ -131,7 +139,10 @@ Module CustomMutexPreds <: MUTEX_PREDS. iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) with "[$OA $OF]") as "[OA OF]". - iModIntro. iFrame "GT OF". iExists th. iFrame. + iDestruct (MutexSets.my_mutexes_alloc_mutex_name γ.(pool_gname) th + (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) + with "Sets") as "[Rest Held]". + iModIntro. iFrame "GT OF Held". iExists th. iFrame. Qed. Lemma state_unlock γ th q : @@ -139,18 +150,21 @@ Module CustomMutexPreds <: MUTEX_PREDS. (|==> state γ false ** not_locked_ghost γ th q). Proof. rewrite /state /owner_token /not_locked_ghost. - iIntros "[State [GT OF]]". + iIntros "[State (GT & OF & Held)]". iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. injection Heq as ->. iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". + iDestruct (MutexSets.my_mutexes_join_mutex_name γ.(pool_gname) th + (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) + with "[$Sets $Held]") as "Sets". iModIntro. iFrame "Sets T". iExists (Some th). iFrame. Qed. Lemma unlocked_owner_token γ th q : state γ false ** owner_token γ th q |-- False. Proof. - rewrite /state /owner_token. iIntros "[State [_ OF]]". + rewrite /state /owner_token. iIntros "[State (_ & OF & _)]". iDestruct "State" as (owner) "(_ & OF0 & _)". iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. @@ -180,7 +194,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. not_locked_ghost γ th q. Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m (Some th) ** + this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m th ** owner_token γ th q. #[global] Hint Opaque token : sl_opacity typeclass_instances. #[global] Hint Opaque locked : typeclass_instances. @@ -201,10 +215,10 @@ Module CustomMutexPreds <: MUTEX_PREDS. Proof. intros q1 q2. rewrite /not_locked /not_locked_ghost. iIntros "[F1 _] [F2 _]". - have Hoverlap : ~ ((↑ mutex_inv_namespace : coPset) ## ↑ mutex_inv_namespace). - { have Hnonempty := nclose_non_empty mutex_inv_namespace. set_solver. } + have Hoverlap : ~ ((↑ mutex_inv_namespace γ : coPset) ## ↑ mutex_inv_namespace γ). + { have Hnonempty := nclose_non_empty (mutex_inv_namespace γ). set_solver. } iDestruct (MutexSets.my_mutexes_exclusive _ th - (↑ mutex_inv_namespace) (↑ mutex_inv_namespace) Hoverlap + (↑ mutex_inv_namespace γ) (↑ mutex_inv_namespace γ) Hoverlap with "[$F1 $F2]") as %[]. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} @@ -219,6 +233,25 @@ Module CustomMutexPreds <: MUTEX_PREDS. WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. + #[global] Instance locked_WeaklyObjective `{Σ : cpp_logic, !G Σ} {σ : genv} + this γ th q : WeaklyObjective (locked this γ th q). + Proof. rewrite /locked /owner_token /MutexTokens.given_token. apply _. Qed. + + Lemma locked_my_mutexes_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt : + locked this g th qt ** + MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. + Proof. + rewrite /locked /owner_token /pool_name /mutex_inv_namespace. + iIntros "[(_ & _ & _ & Held) Names]". + iApply (MutexSets.my_mutexes_exclusive with "[$Held $Names]"). + have Hnonempty := nclose_non_empty (held_namespace g). + have Hsub : (↑held_namespace g : coPset) ⊆ ↑g.(state_namespace). + { rewrite /held_namespace. solve_ndisj. } + set_solver. + Qed. + Section with_Σ. Context `{Σ : cpp_logic, !G Σ}. @@ -230,12 +263,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. atomic.R "int" 1$m (if b then 1 else 0)%Z ** state γ b ** if b then emp else - P ** this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None. + P ** this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id. Definition R {HAS_THREADS : HasStdThreads Σ} {σ : genv} (γ : gname) (q : cQp.t) (P : mpred) : Rep := structR class_name q$m ** as_Rep (fun this => - cinv mutex_inv_namespace (cinv_gname γ) (mutex_inv this γ P) ** + cinv (mutex_inv_namespace γ) (cinv_gname γ) (mutex_inv this γ P) ** cinv_own (cinv_gname γ) q ). #[global] Hint Opaque R : sl_opacity typeclass_instances. @@ -281,17 +314,17 @@ Module CustomMutexPreds <: MUTEX_PREDS. { do_try_lock := do_try_lock }. Lemma init_R (this : ptr) (old : gname) - (pool : iprop.gname) (P : mpred) : + (pool : iprop.gname) (N : namespace) (P : mpred) : WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- - (|={⊤}=> ∃ g, [| pool_name g = pool |] ** + (|={⊤}=> ∃ g, [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). Proof. intros HP. iIntros "(HR & T & P)". iEval (rewrite /R _at_sep _at_as_Rep) in "HR". iDestruct "HR" as "(S & #CI & CO)". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. - iMod (alloc_state pool) as (gs) "(%Hpool & Tnew & Stnew)". + iMod (alloc_state pool N) as (gs) "(%Hpool & Tnew & Stnew)". iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. - iModIntro. iExists (MkGname gs gi). iSplit; first (iPureIntro; exact Hpool). @@ -308,15 +341,13 @@ Module CustomMutexPreds <: MUTEX_PREDS. Qed. Lemma register_thread - (this : ptr) (g : gname) (q : cQp.t) (P : mpred) - (th : thread_idT) (qt : cQp.t) : - this |-> R g q P ** token g qt ** - MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace) ⊣⊢ - this |-> R g q P ** not_locked this g th qt. + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : + token g qt ** MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace g) ⊣⊢ + not_locked this g th qt. Proof. rewrite /not_locked /not_locked_ghost /token /state_token /pool_name. - iSplit; iIntros "($ & ? & ?)"; iFrame. + iSplit; iIntros "[? ?]"; iFrame. Qed. End with_Σ. End CustomMutexPreds. @@ -327,7 +358,7 @@ Module custom_mutex. Module Spec := mutex_spec CustomMutexPreds. Abbreviation N := "MyMutex"%cpp_name. - #[local] Hint Opaque thread_idR : sl_opacity typeclass_instances. + #[local] Hint Opaque thread_id_specs.thread_idR : sl_opacity typeclass_instances. #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : @@ -357,7 +388,7 @@ Module custom_mutex. \prepost{qg} globals qg \pre{(qt : Qp)} not_locked_ghost g thr qt \post P ** - this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** owner_token g thr qt). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( @@ -365,7 +396,7 @@ Module custom_mutex. \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr \prepost{qg} globals qg - \pre{qt} this ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None ** + \pre{qt} this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** owner_token g thr qt \pre ▷P \post not_locked_ghost g thr qt). @@ -427,7 +458,7 @@ Module custom_mutex. std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** if bool_decide (res = 0) then P ** owner_token g thr qt ** - p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None + p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id else not_locked_ghost g thr qt) \end@{mpredI}. Next Obligation. @@ -438,7 +469,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_exchange. iAuIntro1. rewrite /atomic1_acc. - iInv mutex_inv_namespace as "Inv" "Hclose". + iInv (mutex_inv_namespace g) as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -479,7 +510,7 @@ Module custom_mutex. \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P \consuming P - \consuming p ,, _field "MyMutex::m_owner" |-> thread_idR 1$m None + \consuming p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id \consuming{qt} owner_token g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K @@ -493,7 +524,7 @@ Module custom_mutex. iDestruct "IR" as "(S & #CI & CO)". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc /=. - iInv mutex_inv_namespace as "Inv" "Hclose". + iInv (mutex_inv_namespace g) as "Inv" "Hclose". iDestruct "Inv" as "[Inv CO]". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(>L & State & Resources)". @@ -573,8 +604,8 @@ Module custom_mutex. wname [structR] "S". wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". - wname [_ |-> thread_idR _ _] "Owner". - iMod (alloc_state 1%positive) as (gs) "(_ & T & State)". + wname [_ |-> thread_id_specs.thread_idR _ _] "Owner". + iMod (alloc_state 1%positive (nroot .@@ "MyMutex" .@ "inv_namespace")) as (gs) "(_ & T & State)". iMod (cinv_alloc with "[L P Owner State]") as (gi) "[#CI CO]"; last first. - iModIntro. iExists (MkGname gs gi). diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index cf26a0a5..c2c7f865 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -1,80 +1,673 @@ -(** Provisional *) +(** Verification of the recursive wrapper around std::mutex. -Require Import skylabs.auto.cpp.proof. + The implementation clears the atomic owner before releasing the inner + mutex. The contracts are partial-correctness contracts: at the largest + counter value, lock does not return. Hash uniqueness is assumed by + [thread.spec] in the shared thread specifications. The lock/unlock + contracts instantiate [recursive_mutex_spec], including its + [BasicLockable] presentation and construction contracts. *) +Require Import iris.algebra.coPset. +Require Import skylabs.auto.cpp.prelude.proof. +Require Import skylabs.brick.libstdcpp.atomic.spec. +Require Import skylabs.brick.libstdcpp.cassert.spec. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex_atomic. +Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. +Require Import skylabs.brick.libstdcpp.thread.spec. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. -(** FIXME don't need StdRecursiveMutexAtomicSpecs *) -Module StdRecursiveMutexAtomicSpecs := RecursiveMutexAtomicSpecs StdRecursiveMutexName. -Module custom_recursive_mutex. - Abbreviation N := "MyRecursiveMutex"%cpp_name. +NES.Open std.atomic. + +Import linearity. + +Module CustomRecursiveMutexName <: MutexCPPName. + Definition cpp_ty : type := "MyRecursiveMutex"%cpp_type. +End CustomRecursiveMutexName. - (* - sl.lock - Definition countR `{Σ : cpp_logic, σ : genv} (count : nat) : Rep := - (* structR "std::recursive_mutex" 1$m ** *) - _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) +(* A custom recursive mutex implementation built on top of a mutex. + The definition for [token] and [not_locked] coincide with the ones in the + MutexPreds module. *) +Module CustomRecursiveMutexPreds + <: RECURSIVE_MUTEX_PREDS CustomRecursiveMutexName. + Include RecursiveMutexState. - Parameter atomic_thread_idT : ∀ `{Σ : cpp_logic, σ : genv}, cQp.t -> option thread_idT -> Rep. + Definition class_name : name := "MyRecursiveMutex"%cpp_name. + Definition cpp_ty : type := CustomRecursiveMutexName.cpp_ty. + + Record mutex_gname : Set := MkGname { + inner_gname : mutex.gname; + owner_gname : iprop.gname; + cinv_gname : iprop.gname; + }. + Definition gname : Set := mutex_gname. + Definition pool_name (g : gname) : iprop.gname := + mutex.pool_name g.(inner_gname). + Definition rmutex_inv_namespace (g : gname) : namespace := + mutex.mutex_inv_namespace g.(inner_gname). - Parameter exclusive_token : ∀ `{Σ : cpp_logic}, iprop.gname -> mpred. + (** If there is [Some] owner, it records the thread ID [th] and the fraction + in the lock handle [acquireable] for thread [th]. *) + Canonical Structure owner_cmraR : cmra := + excl_authR (optionO (prodO thread_idTO (leibnizO cQp.t))). - Record gname : Set := MkGname - { lock_gname : mutex.gname - ; cinv_gname : iprop.gname - ; excl_gname : iprop.gname - ; rec_gname : StdRecursiveMutexAtomicSpecs.gname + Class stateG `{Σ : cpp_logic} := { + #[global] mutex_G :: mutex.G Σ; + #[local] has_owner_own :: HasOwn (iPropI _Σ) owner_cmraR; + #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; + #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; }. + Definition G := @stateG. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. + + #[global] Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ := _. + + Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := + own γ ((●E o) : owner_cmraR). + Definition owner_tid_frag `{Σ : cpp_logic, !G Σ} + (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := + own γ ((◯E o) : owner_cmraR). + + #[global] Instance owner_tid_auth_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_auth γ o). + Proof. rewrite /owner_tid_auth. apply _. Qed. + #[global] Instance owner_tid_frag_timeless + `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_frag γ o). + Proof. rewrite /owner_tid_frag. apply _. Qed. + + #[global] Instance owner_tid_frag_exclusive + `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). + Proof. + intros o1 o2. rewrite /owner_tid_frag. + iIntros "H1 H2". + iDestruct (own_valid_2 with "H1 H2") as %Hvalid. + move: Hvalid. rewrite excl_auth_frag_op_valid. done. + Qed. + + #[global] Instance owner_tid_auth_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_auth γ o). + Proof. rewrite /owner_tid_auth. apply _. Qed. + #[global] Instance owner_tid_frag_WeaklyObjective + `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_frag γ o). + Proof. rewrite /owner_tid_frag. apply _. Qed. + + #[global] Instance owner_agree `{Σ : cpp_logic, !G Σ} γ o1 o2 : + Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). + Proof. + apply observe_2_intro_only_provable. + rewrite /owner_tid_auth /owner_tid_frag. iIntros "A F". + iDestruct (own_valid_2 with "A F") as %HV. + iPureIntro. apply leibniz_equiv, excl_auth_agree, HV. + Qed. + + Lemma owner_alloc `{Σ : cpp_logic, !G Σ} o : + ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. + Proof. + iMod (own_alloc ((●E o ⋅ ◯E o) : owner_cmraR)) as (γ) "H". + { apply excl_auth_valid. } + iModIntro. iExists γ. + rewrite /owner_tid_auth /owner_tid_frag -own_op. iExact "H". + Qed. + + Lemma owner_update `{Σ : cpp_logic, !G Σ} γ oa ofrag o' : + owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- + (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). + Proof. + rewrite /owner_tid_auth /owner_tid_frag. iIntros "[A F]". + iMod (own_update_2 with "A F") as "[$ $]"; + first apply (excl_auth_update _ _ o'). + done. + Qed. + + #[global] Hint Opaque owner_tid_auth owner_tid_frag : sl_opacity typeclass_instances. + + Definition token `{Σ : cpp_logic, !G Σ} (g : gname) (q : cQp.t) : mpred := + mutex.token g.(inner_gname) q. + #[global] Hint Opaque token : sl_opacity typeclass_instances. + + #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} g : + CFractional (token g). + Proof. intros q1 q2. rewrite /token. apply mutex.token_fractional. Qed. + #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : + Timeless (token g qt). + Proof. rewrite /token. apply _. Qed. + + Definition owner_frag `{Σ : cpp_logic, !G Σ} + (g : gname) (owner : option (thread_idT * cQp.t)) : mpred := + owner_tid_frag g.(owner_gname) owner. - Definition lock_namespace : namespace := nroot .@@ "MyRecursiveMutex". - (* About cinv. *) - Parameter count_auth : ∀ `{Σ : cpp_logic} (count : nat), mpred. - Parameter count_frag : ∀ `{Σ : cpp_logic} (count : nat), mpred. - - (* Definition IR `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !StdRecursiveMutexAtomicSpecs.lockedG Σ} (γ : gname) (q : cQp.t) : mpred := - ∃ x, StdRecursiveMutexAtomicSpecs.owned_count_id_auth γ.(rec_gname) x. *) -(* - Definition rawR `{Σ : cpp_logic, σ : genv} (owner : option thread_idT) (count : nat) : Rep := - structR "std::recursive_mutex" 1$m ** - _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m count. *) - - Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !mutex.G Σ, - !StdRecursiveMutexAtomicSpecs.lockedG Σ}. - - Definition mutex_content (γ : gname) : Rep := - ∃ count, - _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m (Z.of_nat count) ** - pureR ( - count_auth count - (* ** - exclusive_token γ.(excl_gname) *) - ) . - - Definition IR (γ : gname) (q : cQp.t) : Rep := - structR N q$m ** - (* TODO: mutex could take a [Rep] to save us this [as_Rep] *) + Definition countR `{Σ : cpp_logic, σ : genv} + (this : ptr) (n : Z) : mpred := + this ,, _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m n. + + Definition protected `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) (go : iprop.gname) (P : mpred) : mpred := + countR this 0 ** owner_tid_frag go None ** P. + + Definition globals `{Σ : cpp_logic, σ : genv} (q : cQp.t) : mpred := + _global "id_hash" |-> ulongR q (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id). + #[global] Arguments globals /. + + (** Absence of a ghost owner is encoded by the default C++ thread ID. *) + Definition owner_id (owner : option (thread_idT * cQp.t)) : thread_idT := + match owner with + | None => thread_id_specs.default_thread_id + | Some (th, _) => th + end. + + Definition I `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) (g : gname) : mpred := + ∃ owner, + this ,, _field "MyRecursiveMutex::m_owner" |-> + atomic.R Tulong 1$m (thread_id_specs.thread_id_hash (owner_id owner)) ** + owner_tid_auth g.(owner_gname) owner ** + match owner with + | Some (th, qt) => + (** Note that this [mutex.locked] is NOT the [locked] precidate that we + instantiate later for the CustomRecursiveMutexPreds. + + Putting [mutex.locked] here is useful when a thread [th] acquires the + lock for the first time to show that [owner ≠ th]: when [th] opens + the invariant, [th] has [CustomRecursiveMutexPreds.not_locked th] + (which coincide with [mutex.not_locked th]), contradicting + [mutex.locked _ _ th _]. *) + mutex.locked + (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt + | None => emp + end. + + Definition R `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv} + (g : gname) (q : cQp.t) (P : mpred) : Rep := + structR class_name q$m ** as_Rep (fun this => - this ,, _field "MyRecursiveMutex::m_lock" |-> mutex.R γ.(lock_gname) q - (this |-> mutex_content γ) ** - (* *) - (this |-> mutex_content γ - (* ** std_recursive_mutex.token γ.(rec_gname) (* ? *) *) - \\// - exclusive_token γ.(excl_gname) - (* ** std_recursive_mutex.locked γ.(rec_gname) (* ? *) *) - ) ** - cinv lock_namespace γ.(cinv_gname) (∃ count owner, - count_frag count ** - this ,, _field "MyRecursiveMutex::m_owner" |-> atomic_thread_idT 1$m owner ** - StdRecursiveMutexAtomicSpecs.owned_count_id_auth γ.(rec_gname) ((λ t, (t, Nat.pred count)) <$> owner) ** - [| owner = None <-> count = 0 |] ** - match count with - | 0 => emp - | _ => exclusive_token γ.(excl_gname) - end)). - (* we hold the lock if the owner is not none! *) - End with_Σ. + this ,, _field "MyRecursiveMutex::m_lock" |-> + mutex.R g.(inner_gname) q (protected this g.(owner_gname) P) ** + cinv (rmutex_inv_namespace g) g.(cinv_gname) (I this g) ** + cinv_own g.(cinv_gname) q). + #[global] Hint Opaque R : sl_opacity typeclass_instances. + #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive R. + + #[global] Instance R_learnable + `{Σ : cpp_logic, !G Σ, σ : genv, !HasStdThreads Σ} : + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + + Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := + mutex.not_locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt. + + Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) + (n : nat) : mpred := + countR this (Z.of_nat (S n)) ** owner_frag g (Some (th, qt)). + + #[global] Hint Opaque not_locked locked : sl_opacity typeclass_instances. + + #[global] Instance I_timeless + `{Σ : cpp_logic, !G Σ, σ : genv} this g : Timeless (I this g). + Proof. + rewrite /I. apply bi.exist_timeless. + intros [[th qt]|]; apply _. + Qed. + + #[global] Instance owner_frag_timeless + `{Σ : cpp_logic, !G Σ} g owner : Timeless (owner_frag g owner). + Proof. rewrite /owner_frag. apply _. Qed. + #[global] Instance locked_timeless + `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th n : + Timeless (locked this g qt th n). + Proof. rewrite /locked /countR. apply _. Qed. + #[global] Instance not_locked_timeless + `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th : + Timeless (not_locked this g qt th). + Proof. rewrite /not_locked. apply _. Qed. + + #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} + (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : + WeaklyObjective (p |-> R). + Proof. rewrite INTERNAL._at_eq. apply _. Qed. + + #[global] Instance owner_frag_WeaklyObjective + `{Σ : cpp_logic, !G Σ} g owner : WeaklyObjective (owner_frag g owner). + Proof. rewrite /owner_frag. apply _. Qed. + #[global] Instance I_WeaklyObjective + `{Σ : cpp_logic, !G Σ, σ : genv} this g : + WeaklyObjective (I this g). + Proof. + rewrite /I. apply exists_weakly_objective. + intros [[th qt]|]; apply _. + Qed. + #[global] Instance protected_WeaklyObjective + `{Σ : cpp_logic, !G Σ, σ : genv} this go P + `{!WeaklyObjective (countR this 0), !WeaklyObjective P} : + WeaklyObjective (protected this go P). + Proof. rewrite /protected. apply _. Qed. + + Lemma register_thread `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) g th qt : + token g qt ** + MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ + not_locked this g qt th. + Proof. + rewrite /token /not_locked /pool_name /rmutex_inv_namespace. + apply mutex.register_thread. + Qed. + +End CustomRecursiveMutexPreds. + +Module custom_recursive_mutex. + Import CustomRecursiveMutexPreds. + Module Spec := recursive_mutex_spec + CustomRecursiveMutexName CustomRecursiveMutexPreds. + Import Spec (acquireable). + #[local] Hint Opaque acquireable I protected countR : sl_opacity typeclass_instances. + Section with_cpp. + Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !G Σ}. + Context `{MOD : source ⊧ σ}. + + (** The wrapper adds its global sentinel and the count's objectivity + requirement to the shared recursive-mutex construction contract. *) + cpp.spec "MyRecursiveMutex::MyRecursiveMutex()" as ctor_spec with ( + \this this + \prepost{qg} globals qg + \require WeaklyObjective (countR this 0) + \exact Reduce (Spec.ctor_spec this)). + + cpp.spec "MyRecursiveMutex::~MyRecursiveMutex()" as dtor_spec with + (\exact Reduce Spec.dtor_spec). + + cpp.spec "MyRecursiveMutex::lock()" as lock_spec from source with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.lock_spec this)). + + cpp.spec "MyRecursiveMutex::unlock()" as unlock_spec from source with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.unlock_spec this)). + + cpp.spec "MyRecursiveMutex::lock()" as lock_spec_alt from source with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.lock_spec_alt this)). + + cpp.spec "MyRecursiveMutex::unlock()" as unlock_spec_alt from source with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.unlock_spec_alt this)). + + (* Helper specs, proved later. *) + cpp.spec "std::mutex::lock()" as inner_lock_spec with ( + \this inner + \prepost{(container : ptr) g q P} container |-> R g q P + \require inner = container ,, _field "MyRecursiveMutex::m_lock" + \persist{th} current_thread th + \pre{qt} not_locked container g qt th + \post protected container g.(owner_gname) P ** mutex.locked inner g.(inner_gname) th qt). + + cpp.spec "std::mutex::unlock()" as inner_unlock_spec with ( + \this inner + \prepost{(container : ptr) g q P} container |-> R g q P + \require inner = container ,, _field "MyRecursiveMutex::m_lock" + \persist{th} current_thread th + \pre{qt} mutex.locked inner g.(inner_gname) th qt + \pre ▷ protected container g.(owner_gname) P + \post not_locked container g qt th). + + (* The proof bodies in the rest of the section are generated by LLM. *) + Lemma ctor_proof : verify[source] ctor_spec. + Proof using MOD. + (* Initialize the physical members first, with an empty inner invariant. *) + verify_spec. + (* Retain the final statement so its update modality can install the + ghost state after the member constructors return. *) + match goal with |- context[wp ?tu ?rho ?s ?K] => + rewrite (lock (wp tu rho s K)) end. + go. iExists emp. go. + rewrite -lock. + iApply fupd_wp. + iDestruct select (|={⊤}=> _)%I as "Init". + iMod "Init" as (g) "[Inner Token]". + (* Install the zero count, empty owner fragment, and client resource in + the inner mutex; keep the matching authority with the atomic owner. *) + iMod (owner_alloc None) as (go) "[Auth Frag]". + wname [_ |-> ulonglongR _ _] "Count". + iDestruct select (@tele_app TT (fun _ => mpred) P xs) as "P". + iMod (mutex.init_R (this ,, _field "MyRecursiveMutex::m_lock") + g pool N (protected this go (∃ xs, @tele_app TT (fun _ => mpred) P xs)) + with "[Inner Token Count Frag P]") as (inner) "(%Hnames & Inner & Token)". + { iFrame "Inner Token". iNext. rewrite /protected /countR. iFrame. } + wname [_ |-> atomic.R _ _ _] "Owner". + iMod (cinv_alloc ⊤ (mutex.mutex_inv_namespace inner) with "[Owner Auth]") as (gi) "[#CI CO]"; last first. + - iModIntro. go. iExists (MkGname inner go gi). + iSplit; first (iPureIntro; split; reflexivity). + rewrite /token /R _at_sep _at_as_Rep + /I /pool_name /=. + iFrame "CI". iFrame. + - iNext. rewrite /I /=. iExists None. iFrame. + - change (WeaklyObjective (I this (MkGname inner go 1%positive))). + apply _. + Qed. + + Lemma dtor_proof : + mutex.dtor_spec ** std.atomic.dtor Tulong |-- verify[source] dtor_spec. + Proof using MOD. + verify_shift. + rewrite /token /R _at_sep _at_as_Rep. + work. + wname [cinv] "#CI". + wname [cinv_own] "CO". + (* Cancel the owner invariant before destroying the physical members. *) + iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. + iEval (rewrite /I) in "Inv". + iMod "Inv" as (owner) "[Owner State]". + iModIntro. go. + iAssert emp with "[State]" as "_". + { iApply (affine with "[State]"); last iAccu. apply mpred_BiAffine. } + (* The inner mutex destructor returns the protected count and resource. *) + wname [protected] "Guard". + iEval (rewrite /protected /countR) in "Guard". + iDestruct "Guard" as "(Count & Frag & P)". + iAssert emp with "[Frag]" as "_". + { iApply (affine with "[Frag]"); last iAccu. apply mpred_BiAffine. } + ego $usenamed=true with br_erefl. + Qed. + + #[local] Existing Instance mpred_BiAffine. + #[local] Hint Opaque owner_frag locked not_locked + : sl_opacity typeclass_instances. + #[local] Instance owner_frag_learn : LearnEq2 owner_frag. + Proof. solve_learnable. Qed. + + Abbreviation BASE p := + (p ,, _base "std::atomic" "std::__atomic_base"). + + Lemma inner_lock_spec_ok : mutex.lock_spec_alt |-- inner_lock_spec. + Proof. + apply specify_mono. rewrite /R /not_locked. work with br_erefl. + iExists qt. work with br_erefl. + Qed. + + Lemma inner_unlock_spec_ok : mutex.unlock_spec_alt |-- inner_unlock_spec. + Proof. + apply specify_mono. rewrite /R /not_locked. work with br_erefl. + iExists qt. work with br_erefl. + Qed. + + #[program] + Definition load_not_held_C (this : ptr) := + \cancelx + \consuming{g q P} this |-> R g q P + \consuming{th qt} not_locked this g qt th + \guard (th <> thread_id_specs.default_thread_id) + \proving{K (_ : IsExistential K)} + std.atomic.do_load Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) K + \instantiate K := (fun owner => this |-> R g q P ** + not_locked this g qt th ** [| owner <> thread_id_specs.thread_id_hash th |]) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[HR Hhandle]" (?? ->). + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(S & Base & #CI & CO)". + rewrite /std.atomic.do_load. + iAcIntro. rewrite /commit_acc. + iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iEval (rewrite /I) in "Inv". + iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". + iAssert [| option_map fst owner <> Some th |] as %Hne. + { destruct owner as [[owner owner_qt]|]; last (iPureIntro; simpl; congruence). + destruct (decide (owner = th)) as [->|Hne]; last (iPureIntro; simpl; congruence). + iEval (rewrite /not_locked) in "Hhandle". + iDestruct (mutex.Spec.locked_not_locked_exclusive with "[$Handle $Hhandle]") as %[]. } + iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. + iExists (thread_id_specs.thread_id_hash (owner_id owner)), (1$m)%cQp. + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "Owner". iMod "Y" as "_". + iMod ("Hclose" with "[Owner Auth Handle]") as "_". + { iNext. rewrite /I. iExists owner. + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + iPureIntro. intros Heq. + apply thread_id_specs.thread_id_hash_injective in Heq. + destruct owner as [[owner owner_qt]|]; simpl in Heq, Hne; naive_solver. + Qed. + #[local] Hint Resolve load_not_held_C : sl_opacity. + + #[program] + Definition load_held_C (this : ptr) := + \cancelx + \consuming{g q P} this |-> R g q P + \consuming{th qt} owner_frag g (Some (th, qt)) + \proving{K (_ : IsExistential K)} + std.atomic.do_load Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) K + \instantiate K := (fun owner => this |-> R g q P ** + owner_frag g (Some (th, qt)) ** [| owner = thread_id_specs.thread_id_hash th |]) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[HR Hfrag]" (?? ->). + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(S & Base & #CI & CO)". + rewrite /std.atomic.do_load. + iAcIntro. rewrite /commit_acc. + iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iEval (rewrite /I) in "Inv". + iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". + iEval (rewrite /owner_frag) in "Hfrag". + iDestruct (observe_2 [| owner = Some (th, qt) |] with "Auth Hfrag") as %->. + iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. + iExists (thread_id_specs.thread_id_hash th), (1$m)%cQp. + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "Owner". iMod "Y" as "_". + iMod ("Hclose" with "[Owner Auth Handle]") as "_". + { iNext. rewrite /I. iExists (Some (th, qt)). + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". done. + Qed. + #[local] Hint Resolve load_held_C : sl_opacity. + + Lemma store_owner_action (this : ptr) g q P th qt : + this |-> R g q P ** owner_tid_frag g.(owner_gname) None ** + mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt |-- + std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) + (thread_id_specs.thread_id_hash th) (this |-> R g q P ** owner_frag g (Some (th, qt))). + Proof using Type MOD. + iIntros "(HR & Hfrag & Hhandle)". + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(S & Base & #CI & CO)". + rewrite /std.atomic.do_store. + iAcIntro. rewrite /commit_acc. + iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iEval (rewrite /I) in "Inv". + iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". + iEval (rewrite /owner_frag) in "Hfrag". + iDestruct (observe_2 [| owner = None |] with "Auth Hfrag") as %->. + iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. + iExists (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id). + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "Owner". iMod "Y" as "_". + iMod (owner_update (owner_gname g) None None (Some (th, qt)) + with "[$Auth $Hfrag]") as "[Auth Hfrag]". + iMod ("Hclose" with "[Owner Auth Hhandle]") as "_". + { iNext. rewrite /I. iExists (Some (th, qt)). + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + Qed. + + #[program] + Definition store_owner_C (this : ptr) := + \cancelx + \consuming{g q P} this |-> R g q P + \consuming owner_tid_frag g.(owner_gname) None + \consuming{th qt} mutex.locked + (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt + \proving{K (_ : IsExistential K)} + std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) (thread_id_specs.thread_id_hash th) K + \instantiate K := (this |-> R g q P ** owner_frag g (Some (th, qt))) + \end@{mpredI}. + Next Obligation. + intros. iIntros "Hpre" (?? ->). iApply (store_owner_action with "Hpre"). + Qed. + #[local] Hint Resolve store_owner_C : sl_opacity. + + Lemma clear_owner_action (this : ptr) g q P th qt : + this |-> R g q P ** owner_frag g (Some (th, qt)) |-- + std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) + (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id) (this |-> R g q P ** owner_frag g None ** + mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt). + Proof using Type MOD. + iIntros "[HR Hfrag]". + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(S & Base & #CI & CO)". + rewrite /std.atomic.do_store. + iAcIntro. rewrite /commit_acc. + iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iEval (rewrite /I) in "Inv". + iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". + iEval (rewrite /owner_frag) in "Hfrag". + iDestruct (observe_2 [| owner = Some (th, qt) |] with "Auth Hfrag") as %->. + iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. + iExists (thread_id_specs.thread_id_hash th). + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "Owner". iMod "Y" as "_". + iMod (owner_update (owner_gname g) (Some (th, qt)) (Some (th, qt)) None + with "[$Auth $Hfrag]") as "[Auth Hfrag]". + iMod ("Hclose" with "[Owner Auth]") as "_". + { iNext. rewrite /I. iExists None. + iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + Qed. + + #[program] + Definition clear_owner_C (this : ptr) := + \cancelx + \consuming{g q P} this |-> R g q P + \consuming{th qt} owner_frag g (Some (th, qt)) + \proving{K (_ : IsExistential K)} + std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id) K + \instantiate K := (this |-> R g q P ** owner_frag g None ** + mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt) + \end@{mpredI}. + Next Obligation. + intros. iIntros "Hpre" (?? ->). iApply (clear_owner_action with "Hpre"). + Qed. + #[local] Hint Resolve clear_owner_C : sl_opacity. + + Lemma lock_spec_equiv_lock_spec_alt : lock_spec ⊣⊢ lock_spec_alt. + Proof. + rewrite /lock_spec /lock_spec_alt /specify. + do 2 f_equiv. intros this xs K. + rewrite !add_with_equiv. f_equiv=> qg. + by rewrite !add_prepost_equiv Spec.lock_spec_equiv_lock_spec_alt. + Qed. + + Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec ⊣⊢ unlock_spec_alt. + Proof. + rewrite /unlock_spec /unlock_spec_alt /specify. + do 2 f_equiv. intros this xs K. + rewrite !add_with_equiv. f_equiv=> qg. + by rewrite !add_prepost_equiv Spec.unlock_spec_equiv_unlock_spec_alt. + Qed. + + Lemma lock_proof : verify[source] lock_spec. + Proof using MOD. + verify_spec. + rewrite /acquireable. destruct n as [|n xs]. + (* First acquisition: exclude our own ID, then acquire the inner mutex. *) + - ego. + (* Publishing ownership transfers the inner held capability into the + owner invariant; its fragment remembers the registration share. *) + rewrite /protected. ego. + iDestruct select (this |-> R _ _ _) as "HR". + iDestruct select (owner_tid_frag _ None) as "Hfrag". + iDestruct select (mutex.locked _ _ _ _) as "Hinner". + iDestruct (store_owner_action with "[$HR $Hfrag $Hinner]") as "Hstore". + iSplitL "Hstore"; first iExact "Hstore". ego. + rewrite /countR. ego. + rewrite /acquireable /locked /countR. ego. + wname [P] "HP". iExact "HP". + (* Recursive acquisition: the owner fragment identifies this thread. *) + - rewrite /locked /countR. ego. + destruct (decide (Z.of_nat (S n) = 18446744073709551615)%Z) as [Hmax|Hmax]; go. + (* At the counter limit, the loop has no returning execution. *) + + rewrite Hmax. go. wp_for (fun _ => emp); go. + + ego. rewrite /acquireable /locked /countR. + have Hcount : (Z.of_nat (S n) + 1)%Z = Z.of_nat (S (S n)) by lia. + rewrite Hcount. iFrame "#∗". + Qed. + + Lemma unlock_proof : verify[source] unlock_spec. + Proof using MOD. + verify_spec. + rewrite /acquireable /locked /countR. + go. + destruct n as [|n]. + (* Final release: clear the owner to recover the inner held capability; + the inner unlock then returns the acquisition handle. *) + - ego. + (* Return the zero count and client resource to the inner mutex. *) + wname [mutex.locked] "Locked". iFrame "Locked". + wname [P] "P". + wname [_ |-> ulonglongR _ _] "Count". + wname [owner_frag] "OF". + iSplitL "P Count OF". + { iNext. rewrite /protected /countR /owner_frag. + iFrame "Count OF". iExists args. iExact "P". } + ego. rewrite /acquireable release.unlock. iFrame "#∗". + (* Nested release: decrement the count and keep the resource. *) + - ego. rewrite /acquireable release.unlock /locked /countR. + have Hcount : (Z.of_nat (S (S n)) - 1)%Z = Z.of_nat (S n) by lia. + rewrite Hcount. iFrame "#∗". + Qed. + + Lemma lock_alt_proof : verify[source] lock_spec_alt. + Proof using MOD. + rewrite -lock_spec_equiv_lock_spec_alt. exact lock_proof. + Qed. + + Lemma unlock_alt_proof : verify[source] unlock_spec_alt. + Proof using MOD. + rewrite -unlock_spec_equiv_unlock_spec_alt. exact unlock_proof. + Qed. + + Local Lemma id_dtor_ok : verify[source] thread_id_specs.thread_id_dtor_spec. + Proof using MOD. verify_spec. rewrite thread_id_specs.thread_idR.unlock. go. Qed. + + (** Only the out-of-line hash primitive and native-ID call remain library + premises for thread IDs. The hash wrappers and destructors are proved + in [thread.spec]. *) + Definition standard_library_specs : mpred := + denoteModule thread_hpp.source ** + mutex.ctor_spec ** mutex.dtor_spec ** + mutex.lock_spec_alt ** mutex.unlock_spec_alt ** + std.atomic.ctor Tulong ** std.atomic.dtor Tulong ** + std.atomic.cast Tulong ** std.atomic.assign Tulong ** + thread_id_specs.get_id_spec ** + thread_id_specs.hash_bytes_spec ** + std.cassert.assert_fail_spec. + + Lemma link : + denoteModule source ** standard_library_specs |-- + ctor_spec ** dtor_spec ** lock_spec ** unlock_spec. + Proof using MOD. + rewrite /standard_library_specs. + iIntros "[Hsource [#Hthread Hspecs]]". + iDestruct (observe [| thread_hpp.source ⊧ σ |] with "Hthread") as "%THREADMOD". + iCombine "Hsource Hthread Hspecs" as "Hpre". iRevert "Hpre". + work. + wapply ctor_proof. + wapply dtor_proof. + wapply lock_proof. + wapply unlock_proof. + wapply inner_lock_spec_ok. + wapply inner_unlock_spec_ok. + wapply id_dtor_ok. + wapply (thread_id_specs.hash_link (MOD := THREADMOD)). + work. + Qed. + + End with_cpp. End custom_recursive_mutex. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v index b9b48daa..5c9aebec 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -103,11 +103,10 @@ Section with_cpp. iModIntro. iDestruct "Hstart" as "(Hm & Htoken & Hfrac)". iDestruct (MutexSets.my_mutexes_alloc_mutex_name (mutex.pool_name g) - child ⊤ (↑mutex.mutex_inv_namespace) ltac:(set_solver) + child ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "Hset") as "[Hrest Hname]". - iCombine "Hm Htoken Hname" as "Hm". - iEval (rewrite mutex.register_thread) in "Hm". - iDestruct "Hm" as "[Hm Hnotlocked]". + iCombine "Htoken Hname" as "Hnotlocked". + iEval (rewrite (mutex.register_thread (_global "m"))) in "Hnotlocked". iEval (unfold double_incr_spec_body, specify, unmaterialized_specR) in "Hcode". iApply (invoke.use_cptrR with "Hcode"). cbn. iSplitR; first done. @@ -146,11 +145,11 @@ Section with_cpp. iMod (thread.spec.spawned_threads_inv_alloc thread_pool_namespace) as (pool) "#Hpool". (* Logical invariant setup for the already initialized mutex. *) - iMod (mutex.init_R (_global "m") γold pool + iMod (mutex.init_R (_global "m") γold pool (mutex.mutex_inv_namespace γold) (double_incr_protocol.protected γ (_global "x")) ltac:(apply _) - with "[Hm Htoken HP]") as (g) "(%Hpool_name & Hm & Htoken)". + with "[Hm Htoken HP]") as (g) "(%Hnames & Hm & Htoken)". { iFrame "Hm Htoken". iNext. done. } - subst pool. + destruct Hnames as [Hpool_name _]. subst pool. iDestruct "Hm" as "[Hm1 Hm2]". iEval (rewrite (cfractional_split_half (mutex.token g)) cQp.scale_mut Qp.mul_1_r) in "Htoken". From 67703774c4aec28b310488f13e04af32658a1df0 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 8 Oct 2026 22:08:12 +0000 Subject: [PATCH 078/109] lock_ghost2.v: change Props in module type to axioms --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index fa6c5787..45caa78e 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -41,35 +41,35 @@ Module Type MUTEX_SETS. #[global] Declare Instance my_mutexes_objective `{Σ : cpp_logic, !G Σ} γ th E : Objective (my_mutexes γ th E). - Parameter my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), + Axiom my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), ~(E1 ## E2) -> my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2) |-- False. - Parameter alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, + Axiom alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, ⊢ |==> ∃ γ, mutex_set_map γ ∅. - Parameter mutex_sets_alloc_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, + Axiom mutex_sets_alloc_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, th ∉ T -> mutex_set_map γ T |-- (|==> mutex_set_map γ (T ∪ {[th]}) ** my_mutexes γ th (CoPset ⊤)). - Parameter mutex_sets_free_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, + Axiom mutex_sets_free_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, th ∈ T -> mutex_set_map γ T ** my_mutexes γ th (CoPset ⊤) |-- mutex_set_map γ (T \ {[th]}). - Parameter mutex_set_map_empty_no_my_mutexes : + Axiom mutex_set_map_empty_no_my_mutexes : forall `{Σ : cpp_logic, !G Σ} γ th (N : namespace), mutex_set_map γ ∅ ** my_mutexes γ th (CoPset (↑N)) |-- False. - Parameter my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} + Axiom my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} γ th (E N : coPset), N ⊆ E -> my_mutexes γ th (CoPset E) |-- my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). - Parameter my_mutexes_split : forall `{Σ : cpp_logic, !G Σ} + Axiom my_mutexes_split : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2 : coPset), E1 ## E2 -> my_mutexes γ th (CoPset (E1 ∪ E2)) ⊣⊢ my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2). - Parameter my_mutexes_join_mutex_name : forall `{Σ : cpp_logic, !G Σ} + Axiom my_mutexes_join_mutex_name : forall `{Σ : cpp_logic, !G Σ} γ th (E N : coPset), N ⊆ E -> my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N) |-- @@ -132,16 +132,16 @@ Module Type MUTEX_TOKENS. #[global] Declare Instance token_not_full_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ : WeaklyObjective (token_not_full γ). - Parameter token_full_init : forall `{Σ : cpp_logic, !G Σ} γ, + Axiom token_full_init : forall `{Σ : cpp_logic, !G Σ} γ, given_token γ 1 |-- token_full γ. - Parameter acquire : forall `{Σ : cpp_logic, !G Σ} γ q, + Axiom acquire : forall `{Σ : cpp_logic, !G Σ} γ q, token_full γ ** token γ q |-- given_token γ q ** token_not_full γ. - Parameter release : forall `{Σ : cpp_logic, !G Σ} γ q, + Axiom release : forall `{Σ : cpp_logic, !G Σ} γ q, token_not_full γ ** given_token γ q |-- token γ q ** token_full γ. - Parameter token_not_full_full_token : forall `{Σ : cpp_logic, !G Σ} γ, + Axiom token_not_full_full_token : forall `{Σ : cpp_logic, !G Σ} γ, token_not_full γ ** token γ 1 |-- False. - Parameter alloc : forall `{Σ : cpp_logic, !G Σ}, + Axiom alloc : forall `{Σ : cpp_logic, !G Σ}, ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. End MUTEX_TOKENS. @@ -173,9 +173,9 @@ Module Type OWNER_TID. `{Σ : cpp_logic, !G Σ} γ o1 o2 : Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). - Parameter alloc : forall `{Σ : cpp_logic, !G Σ} o, + Axiom alloc : forall `{Σ : cpp_logic, !G Σ} o, ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. - Parameter owner_update : forall `{Σ : cpp_logic, !G Σ} γ oa ofrag o', + Axiom owner_update : forall `{Σ : cpp_logic, !G Σ} γ oa ofrag o', owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). End OWNER_TID. From 415117540e14ae9d19e99328a5f1a18714d2d2d6 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 8 Oct 2026 22:13:28 +0000 Subject: [PATCH 079/109] lock_ghost2.v: mutex_sets_alloc_thread implementation does not need the update --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 29 ++++++++++++------- .../proof/mutex/spec/recursive_mutex.v | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 45caa78e..f0cd4e43 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -248,18 +248,17 @@ Module MutexSets : MUTEX_SETS. iModIntro. iExists γ. iExact "Hmap". Qed. - Lemma mutex_sets_alloc_thread γ T th : + (* does not need the update, but is implementation detail. *) + Lemma mutex_sets_alloc_thread' γ T th : th ∉ T -> mutex_set_map γ T |-- - (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (CoPset ⊤)). + mutex_set_map γ (T ∪ {[th]}) ** my_mutexes γ th (CoPset ⊤). Proof. - rewrite /mutex_set_map /my_mutexes. - iIntros (Hfresh) "Hmap". - iMod (own_update γ _ (reserve (T ∪ {[th]}) ⋅ - discrete_fun_singleton th (CoPset ⊤)) - with "Hmap") as "[Hmap Ht]". - { apply discrete_fun_update. intros th'. + intros Hfresh. + rewrite /mutex_set_map /my_mutexes -own_op. + have Heq : reserve T ≡ + reserve (T ∪ {[th]}) ⋅ discrete_fun_singleton th (CoPset ⊤). + { intros th'. rewrite discrete_fun_lookup_op. destruct (decide (th = th')) as [<-|Hne]. - rewrite discrete_fun_lookup_singleton /reserve. @@ -272,7 +271,17 @@ Module MutexSets : MUTEX_SETS. + rewrite !decide_True; try set_solver. + rewrite !decide_False; try set_solver. } - iModIntro. iFrame. + by rewrite Heq. + Qed. + + Lemma mutex_sets_alloc_thread γ T th : + th ∉ T -> + mutex_set_map γ T |-- + (|==> mutex_set_map γ (T ∪ {[th]}) ** + my_mutexes γ th (CoPset ⊤)). + Proof. + iIntros (Hfresh) "Hmap". iModIntro. + iApply (mutex_sets_alloc_thread' γ T th Hfresh with "Hmap"). Qed. Lemma mutex_sets_free_thread γ T th : diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 0b2ebbde..ff543adf 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -137,7 +137,7 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). (** FIXME maybe not_locked should just be a definition in the spec section? *) - Parameter register_thread : forall + Axiom register_thread : forall (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t), token g qt ** MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ From 6614f59743ad9fcbd996db8f3d7228526819f7e5 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 8 Oct 2026 22:24:42 +0000 Subject: [PATCH 080/109] add a comment --- rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v | 1 + 1 file changed, 1 insertion(+) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index ff543adf..f15ee8b5 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -99,6 +99,7 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Existing Class G. #[global] Arguments G {_ _} Σ : assert. + (* FIXME (pool_name g) should be a singleton gname *) (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) Parameter pool_name : gname -> iprop.gname. Parameter rmutex_inv_namespace : gname -> namespace. From 1008fae2c2db9a97dcd9e824729ccfb6488ef21b Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 8 Oct 2026 23:22:21 +0000 Subject: [PATCH 081/109] in [mutex.v] and [recursive_mutex.v]: remove register_thread, define not_locked directly --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 41 +++++++----- .../proof/mutex/spec/recursive_mutex.v | 16 ++--- .../proof/mutex/spec/recursive_mutex_atomic.v | 6 -- .../test/mutex/custom_mutex_hpp_proof.v | 63 ++++++++----------- .../mutex/custom_recursive_mutex_hpp_proof.v | 10 --- .../test/mutex/double_incr_cpp_proof.v | 5 +- .../test/mutex/guard_recursive_cpp_proof.v | 4 +- .../mutex/transfer_with_mutex_cpp_proof.v | 10 +-- 8 files changed, 66 insertions(+), 89 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 60ebbf95..3d09c545 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -16,14 +16,17 @@ Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Import linearity. -(** A MUTEX_PREDS module says a mutex spec is parametrized by some `token`, - `not_locked` and `locked`. The exact model depends on the implementation. *) +(** A MUTEX_PREDS module says a mutex spec is parametrized by some `token` and + `locked`. The exact model depends on the implementation. *) Module Type MUTEX_PREDS. Parameter gname : Set. Parameter cpp_ty : type. Parameter mutex_inv_namespace : gname -> namespace. + (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) + Parameter pool_name : gname -> iprop.gname. + Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. #[global] Arguments G {_ _} Σ : assert. @@ -35,20 +38,36 @@ Module Type MUTEX_PREDS. The permission might involve points-to, so use cQp.t instead of QP. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. - Parameter not_locked locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := + token g qt ** MutexSets.my_mutexes (pool_name g) th + (coPset.CoPset $ ↑mutex_inv_namespace g). + #[global] Hint Opaque not_locked : sl_opacity typeclass_instances. + + Parameter locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, ptr -> gname -> thread_idT -> cQp.t -> mpred. #[global] Declare Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). - #[global] Declare Instance not_locked_timeless + #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (not_locked this γ th q). + Proof. rewrite /not_locked. apply _. Qed. (** Each thread has at most one handle to attempt locking this mutex. *) - #[global] Declare Instance not_locked_exclusive + #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th : Exclusive1 (not_locked this γ th). + Proof. + intros q1 q2. rewrite /not_locked. + iIntros "[_ H1] [_ H2]". + have Hoverlap : ~ ((↑mutex_inv_namespace γ : coPset) ## ↑mutex_inv_namespace γ). + { have Hnonempty := nclose_non_empty (mutex_inv_namespace γ). set_solver. } + iDestruct (MutexSets.my_mutexes_exclusive _ th + (↑mutex_inv_namespace γ) (↑mutex_inv_namespace γ) Hoverlap + with "[$H1 $H2]") as %[]. + Qed. #[global] Declare Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). @@ -60,9 +79,6 @@ Module Type MUTEX_PREDS. `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : WeaklyObjective (locked this γ th q). - (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) - Parameter pool_name : gname -> iprop.gname. - Section with_cpp. Context `{Σ : cpp_logic}. @@ -125,13 +141,6 @@ Module Type MUTEX_PREDS. [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). - (** FIXME maybe not_locked should just be a definition. *) - Axiom register_thread : forall - (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t), - token g qt ** MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace g) ⊣⊢ - not_locked this g th qt. - (** FIXME do we really need this? *) Axiom locked_my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} {σ : genv} this g th qt, @@ -156,7 +165,7 @@ Section with_cpp. Lemma locked_not_locked_exclusive (this : ptr) g th qt qt' : locked this g th qt ** not_locked this g th qt' |-- False. Proof. - rewrite -register_thread. iIntros "[HL [_ Hnames]]". + rewrite /not_locked. iIntros "[HL [_ Hnames]]". iApply (locked_my_mutexes_exclusive with "[$HL $Hnames]"). Qed. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index f15ee8b5..fe14b9ee 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -110,8 +110,10 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - Parameter not_locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, - ptr -> gname -> cQp.t -> thread_idT -> mpred. + Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := + token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑rmutex_inv_namespace g). Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. @@ -126,8 +128,9 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. - #[global] Declare Instance not_locked_timeless {σ : genv} this g qt th : + #[global] Instance not_locked_timeless {σ : genv} this g qt th : Timeless (not_locked this g qt th). + Proof. rewrite /not_locked. apply _. Qed. Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. @@ -137,13 +140,6 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). #[only(cfractional,cfracvalid,ascfractional)] derive R. #[global] Declare Instance R_type_ptr g q P : Observe (type_ptrR cpp_ty) (R g q P). - (** FIXME maybe not_locked should just be a definition in the spec section? *) - Axiom register_thread : forall - (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t), - token g qt ** MutexSets.my_mutexes (pool_name g) th - (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - not_locked this g qt th. - End with_cpp. End RECURSIVE_MUTEX_PREDS. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index ed15c2d3..98507443 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -394,12 +394,6 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Context `{!HasStdThreads Σ} {σ : genv}. - Lemma register_thread (this : ptr) g th qt : - token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - not_locked this g qt th. - Proof. by rewrite /not_locked. Qed. - Lemma locked_contradict_full_token (this : ptr) g q qt th n P : this |-> R g q P ** token g 1$m ** locked this g qt th n |-- (|={⊤}=> False). diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 8bd382b4..80084c7a 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -58,10 +58,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. (γ : state_gname) (q : cQp.t) : mpred := MutexTokens.token γ.(token_gname) q. - Definition not_locked_ghost `{Σ : cpp_logic, !G Σ} - (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := - MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑γ.(state_namespace)) ** - MutexTokens.token γ.(token_gname) q. + Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := + state_token γ q. + Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := + token γ q ** + MutexSets.my_mutexes (pool_name γ) th (coPset.CoPset $ ↑mutex_inv_namespace γ). Definition owner_token `{Σ : cpp_logic, !G Σ} (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := @@ -129,12 +131,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. iApply MutexTokens.token_full_init. iExact "GT". Qed. - Lemma state_lock γ th q : - state γ false ** not_locked_ghost γ th q |-- + Lemma state_lock {σ : genv} this γ th q : + state γ false ** not_locked this γ th q |-- (|==> state γ true ** owner_token γ th q). Proof. - rewrite /state /not_locked_ghost /owner_token. - iIntros "[State [Sets T]]". + rewrite /state /not_locked /token /state_token /pool_name /mutex_inv_namespace /owner_token. + iIntros "[State [T Sets]]". iDestruct "State" as (owner) "(OA & OF & Balance)". iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) @@ -145,11 +147,11 @@ Module CustomMutexPreds <: MUTEX_PREDS. iModIntro. iFrame "GT OF Held". iExists th. iFrame. Qed. - Lemma state_unlock γ th q : + Lemma state_unlock {σ : genv} this γ th q : state γ true ** owner_token γ th q |-- - (|==> state γ false ** not_locked_ghost γ th q). + (|==> state γ false ** not_locked this γ th q). Proof. - rewrite /state /owner_token /not_locked_ghost. + rewrite /state /owner_token /not_locked /token /state_token /pool_name /mutex_inv_namespace. iIntros "[State (GT & OF & Held)]". iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. @@ -178,7 +180,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. Qed. End state_laws. - #[global] Hint Opaque state_token not_locked_ghost owner_token state : sl_opacity typeclass_instances. + #[global] Hint Opaque state_token not_locked owner_token state : sl_opacity typeclass_instances. Definition globals `{Σ : cpp_logic} {σ : genv} (q : cQp.t) : mpred := _global "std::memory_order_seq_cst" |-> @@ -187,18 +189,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. #[global] Arguments globals /. - Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := - state_token γ q. - Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} - (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := - not_locked_ghost γ th q. Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m th ** owner_token γ th q. #[global] Hint Opaque token : sl_opacity typeclass_instances. #[global] Hint Opaque locked : typeclass_instances. - #[global] Arguments not_locked /. #[global] Arguments locked /. #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : @@ -209,12 +205,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. Proof. rewrite /token. apply _. Qed. #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : Timeless (not_locked this γ th q). - Proof. rewrite /not_locked /not_locked_ghost. apply _. Qed. + Proof. rewrite /not_locked. apply _. Qed. #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th : Exclusive1 (not_locked this γ th). Proof. - intros q1 q2. rewrite /not_locked /not_locked_ghost. - iIntros "[F1 _] [F2 _]". + intros q1 q2. rewrite /not_locked. + iIntros "[_ F1] [_ F2]". have Hoverlap : ~ ((↑ mutex_inv_namespace γ : coPset) ## ↑ mutex_inv_namespace γ). { have Hnonempty := nclose_non_empty (mutex_inv_namespace γ). set_solver. } iDestruct (MutexSets.my_mutexes_exclusive _ th @@ -340,15 +336,6 @@ Module CustomMutexPreds <: MUTEX_PREDS. - apply _. Qed. - Lemma register_thread - (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : - token g qt ** MutexSets.my_mutexes (pool_name g) th - (coPset.CoPset $ ↑mutex_inv_namespace g) ⊣⊢ - not_locked this g th qt. - Proof. - rewrite /not_locked /not_locked_ghost /token /state_token /pool_name. - iSplit; iIntros "[? ?]"; iFrame. - Qed. End with_Σ. End CustomMutexPreds. @@ -386,7 +373,7 @@ Module custom_mutex. \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr \prepost{qg} globals qg - \pre{(qt : Qp)} not_locked_ghost g thr qt + \pre{qt} not_locked this g thr qt \post P ** this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** owner_token g thr qt). @@ -399,7 +386,7 @@ Module custom_mutex. \pre{qt} this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** owner_token g thr qt \pre ▷P - \post not_locked_ghost g thr qt). + \post not_locked this g thr qt). cpp.spec "MyMutex::lock()" as lock_spec_alt with (\this this @@ -453,13 +440,13 @@ Module custom_mutex. \using denoteModule source \using{thr} current_thread thr \consuming{g q P} p |-> IR g q P - \consuming{qt} not_locked_ghost g thr qt + \consuming{qt} not_locked p g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** if bool_decide (res = 0) then P ** owner_token g thr qt ** p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id - else not_locked_ghost g thr qt) + else not_locked p g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -493,7 +480,7 @@ Module custom_mutex. iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. iPureIntro. auto. + iDestruct "Resources" as "[P Owner]". - iMod (state_lock _ with "[$State $NL]") as "[State Locked]". + iMod (state_lock p _ with "[$State $NL]") as "[State Locked]". iMod ("Hclose" with "[L State]") as "_". { iNext. rewrite /mutex_inv. iExists true. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -514,7 +501,7 @@ Module custom_mutex. \consuming{qt} owner_token g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K - \instantiate K := (p |-> IR g q P ** not_locked_ghost g thr qt) + \instantiate K := (p |-> IR g q P ** not_locked p g thr qt) \end@{mpredI}. Next Obligation. intros. iIntros "[#M Hpre]" (?? ->). @@ -533,7 +520,7 @@ Module custom_mutex. iSplitL "L"; first by ework $usenamed=true with br_erefl. iNext. iIntros "L". iMod "Y" as "_". destruct b. - - iMod (state_unlock _ with "[$State $Locked]") as "[State NL]". + - iMod (state_unlock p _ with "[$State $Locked]") as "[State NL]". iMod ("Hclose" with "[L State P Owner]") as "_". { iNext. rewrite /mutex_inv. iExists false. iSplitL "L"; first by ework $usenamed=true with br_erefl. @@ -584,7 +571,7 @@ Module custom_mutex. Lemma mymutex_lock_alt_proof : verify[source] lock_spec_alt. Proof using MOD HAS_THREADS. verify_spec; ego. - wname [CustomMutexPreds.not_locked_ghost] "NL". + wname [CustomMutexPreds.not_locked] "NL". iFrame "NL". ego. Unshelve. all: exact (1$m)%cQp. Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index c2c7f865..afe26f30 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -246,16 +246,6 @@ Module CustomRecursiveMutexPreds WeaklyObjective (protected this go P). Proof. rewrite /protected. apply _. Qed. - Lemma register_thread `{Σ : cpp_logic, !G Σ, σ : genv} - (this : ptr) g th qt : - token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g) ⊣⊢ - not_locked this g qt th. - Proof. - rewrite /token /not_locked /pool_name /rmutex_inv_namespace. - apply mutex.register_thread. - Qed. - End CustomRecursiveMutexPreds. Module custom_recursive_mutex. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v index 5c9aebec..b101b271 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -105,8 +105,9 @@ Section with_cpp. iDestruct (MutexSets.my_mutexes_alloc_mutex_name (mutex.pool_name g) child ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "Hset") as "[Hrest Hname]". - iCombine "Htoken Hname" as "Hnotlocked". - iEval (rewrite (mutex.register_thread (_global "m"))) in "Hnotlocked". + iAssert (mutex.not_locked (_global "m") g child ((1 / 2)$m)%cQp) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /mutex.not_locked. iFrame. } iEval (unfold double_incr_spec_body, specify, unmaterialized_specR) in "Hcode". iApply (invoke.use_cptrR with "Hcode"). cbn. iSplitR; first done. diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index f022e72a..03ac957f 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -179,7 +179,7 @@ Section with_cpp. (TT := TT) t 1$m thr std_recursive_mutex.NotHeld (P c_addr))%I with "[Htoken Hname]" as "Hacquireable". { rewrite /std_recursive_mutex.acquireable /= - -std_recursive_mutex.register_thread. iFrame "#∗". } + /std_recursive_mutex.not_locked. iFrame "#∗". } iDestruct "Hacquireable" as "?". go. destruct args as [a []]. rewrite P.unlock /=. @@ -217,7 +217,7 @@ Section with_cpp. match goal with g : std_recursive_mutex.gname |- _ => rename g into γ end. iDestruct select (std_recursive_mutex.acquireable _ _ _ _ _ _) as "Hacquireable". iEval (rewrite /std_recursive_mutex.acquireable /= - -std_recursive_mutex.register_thread) in "Hacquireable". + /std_recursive_mutex.not_locked) in "Hacquireable". iDestruct "Hacquireable" as "(_ & Htoken & Hname)". iDestruct (MutexSets.my_mutexes_join_mutex_name (std_recursive_mutex.pool_name γ) thr ⊤ diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index c17a5289..dff652b2 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -224,7 +224,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Htoken Hnames]" as "Hacquireable". - { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. + { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "#∗". } iExists f, g, q, (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th @@ -258,7 +258,7 @@ Section class_C. - iIntros "(HR & Hacquireable & HK)". iApply "Hpost". iFrame "HK". iEval (rewrite /std_recursive_mutex.acquireable /= - -std_recursive_mutex.register_thread) in "Hacquireable". + /std_recursive_mutex.not_locked) in "Hacquireable". iDestruct "Hacquireable" as "(_ & Htoken & Hnames)". destruct Hg as [Hgpool HN]. iEval (rewrite Hgpool HN) in "Hnames". iFrame "Hnames". @@ -306,7 +306,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Hfrom_names Hfrom_token]" as "?". - { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. iFrame "#∗". } + { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. iFrame "#∗". } iDestruct select (MutexSets.my_mutexes _ th (coPset.CoPset (↑to_namespace))) as "Hto_names". iEval (rewrite -a -b0) in "Hto_names". @@ -314,7 +314,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (to ,, _field "C::mut") (TT := balance_args) g qto th std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I with "[Hto_names Hto_token]" as "?". - { rewrite /std_recursive_mutex.acquireable /= -std_recursive_mutex.register_thread. iFrame "#∗". } + { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. iFrame "#∗". } go. rewrite std_recursive_mutex.acquire.unlock in H1, H2. destruct H1 as [[from_balance []] Hfrom]. inversion Hfrom; subst. @@ -331,7 +331,7 @@ Section class_C. std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I. rewrite std_recursive_mutex.release.unlock /=. go. rewrite accountR.unlock /std_recursive_mutex.acquireable /= - -!std_recursive_mutex.register_thread. go with br_erefl. + /std_recursive_mutex.not_locked. go with br_erefl. rewrite a b b0. go. Qed. From f532382a7f3ff2f7aa472f9bc7acfd0dd3e3ec80 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 03:19:16 +0000 Subject: [PATCH 082/109] [shared_mutex.v] refactor to use [my_mutex] and follow similar style as mutex.v; [smutex_inv] is for shared_mutex implementation proof and deleted in the specs --- .../proof/shared_mutex/shared_mutex.v | 640 +++++++++--------- 1 file changed, 318 insertions(+), 322 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v index 423054ea..a7ef647a 100644 --- a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v +++ b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v @@ -1,7 +1,7 @@ +Require Import iris.algebra.coPset. + Require Import skylabs.bi.tls_modalities. Require Import skylabs.bi.tls_modalities_rep. -Require Import skylabs.bi.weakly_objective. -Require Import skylabs.auto.cpp.weakly_local_with. Require Import skylabs.auto.cpp.spec. Require Import skylabs.auto.cpp.proof. @@ -9,325 +9,321 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.shared_mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. -Require Import skylabs.brick.libstdcpp.lib.tactics. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Import linearity. -Import lock_ghost. - -Module shared_mutex. -Section with_cpp. - - (* maps th:thread_idT to the fraction of permission borrowed. *) - Canonical Structure phys_stateUR := authR (gmapR thread_idT Qp). - - - Class lockedG `{Σ : cpp_logic} := { - #[local] has_lock_ghost :: lock_ghost.lockG Σ; - - #[local] has_phys_state :: HasOwn (iPropI _Σ) phys_stateUR; - #[local] has_phys_state_upd :: HasOwnUpd (iPropI _Σ) phys_stateUR; - #[local] has_phys_state_valid :: HasOwnValid (iPropI _Σ) phys_stateUR; - }. - - #[global] Arguments lockedG {_ _} Σ : assert. - - Record gname : Set := MkGname - { phys_state_gname : iprop.gname; - login_gname : iprop.gname; - inv_gname : iprop.gname; - }. - - Definition reader_locked `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (th : thread_idT) (q : Qp) : mpred := - own γ.(phys_state_gname) (◯ {[ th := q ]}). - - (** Adapted from https://gitlab.mpi-sws.org/iris/iris/-/blob/master/iris_heap_lang/lib/rw_spin_lock.v?ref_type=heads - - Allows to prove writer_writer_exclusive and writer_reader_exclusive. - Unlike them, we can't just use ∅ for the reader map, because when the - writer unlocks, the cinv stores its `user γ th`, but `th` is existentially - quanified in cinv and it needs some way to recover that `th` matches with - itself. Note that we still lose writer_reader_exclusiveness for the same, - thread, but that should be fine with the current cinv. - *) - Definition locked `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (th : thread_idT) : mpred := - own γ.(phys_state_gname) (●{# 3/4} {[ th := 1%Qp ]}). - - Local Lemma writer_writer_exclusive `{Σ : cpp_logic, !lockedG Σ} - γ th1 th2 : - locked γ th1 -∗ locked γ th2 -∗ False. - Proof. - iIntros "H1 H2". - iDestruct (own_valid_2 with "H1 H2") as "%Hvalid". - exfalso. - rewrite - auth_auth_dfrac_op_valid - dfrac_op_own - dfrac_valid_own in Hvalid. - by destruct Hvalid as [? _]. - Qed. - - Local Lemma reader_writer_exclusive `{Σ : cpp_logic, !lockedG Σ} - γ th1 th2 q: - th1 <> th2 -> - reader_locked γ th1 q -∗ locked γ th2 -∗ False. - Proof. - iIntros (Hneq) "H1 H2". - iDestruct (own_valid_2 with "H2 H1") as "%Hvalid". - exfalso. - apply auth_both_dfrac_valid in Hvalid as (_ & Hvalid & _). - generalize (Hvalid 0)=> H. - apply singleton_includedN_l in H as (? & ? & ?). - rewrite lookup_singleton in H. - destruct decide in H; try done. - inv H. - Qed. - - Abbreviation used_threads γ s := - (lock_ghost.used_threads γ.(login_gname) s). - - Abbreviation users γ ths := - (lock_ghost.users γ.(login_gname) ths). - - Abbreviation user γ th := (users γ {[ th ]}). - - Context `{Σ : cpp_logic}. - Context `{!lockedG Σ}. - Context `{!HasStdThreads Σ}. - - #[global] Instance - locked_WeaklyObjective γ thr : - WeaklyObjective (PROP := iPropI _) (locked γ thr). - Proof. (* locked.unlock. *) apply _. Qed. - - #[global] Hint Opaque locked : sl_opacity typeclass_instances. - - #[global] Instance - reader_locked_WeaklyObjective γ thr n : - WeaklyObjective (PROP := iPropI _) (reader_locked γ thr n). - Proof. (* locked.unlock. *) apply _. Qed. - - #[global] Hint Opaque reader_locked : sl_opacity typeclass_instances. - - (** A resource enforcing that the thread calling unlock must be the same thread - that owns the lock - - << - \persist{th} >={ L_TI } th - \pre{j} mutex_locked g j - test_unlock(std::mutex & m) { - m.unlock(); - } - >> - - this succeeds: - - << - \persist{th} >={ L_TI } th - \pre mutex_locked g th - same test_unlock - >> - *) - - Context `{MOD : source ⊧ σ}. - - Definition smutex_N : namespace := - nroot .@@ "std" .@@ "shared_mutex" .@ "raw_inv". - - Section with_Qcanon. - Import Qcanon. - - Definition oqp_to_qc (oqp : option Qp) : Qc := - match oqp with - | Some qP => Qp_to_Qc qP - | None => 0%Qc - end. - - Definition smutex_inv γ (P : Qp -> mpred) : mpred := - ∃ (oqP : option Qp) ths borrow_map, - match oqP with - | Some qP => P qP - | None => emp - end ** - (* the set of borrowers *) - users γ ths ** - (* writer mode: cinv keeps ●{# 1/4} of the borrow map and the frac so - writer can't call shared_unlock(). writer has ●{# 3/4}. - reader mode: cinv keeps the full borrow map, readers has the fracs. *) - (( - own γ.(phys_state_gname) (●{# 1/4} borrow_map) ∗ - own γ.(phys_state_gname) (◯ borrow_map) ∧ - ∃ th, [| borrow_map = {[ th := 1%Qp ]} |] - ) ∨ - own γ.(phys_state_gname) (● borrow_map)) ** - (* the permission borrowed and the permission left in the inv sums to 1 *) - [| map_fold (λ _ qi q, (Qp_to_Qc qi) + q) (oqp_to_qc oqP) borrow_map = 1%Qc |] ** - (* all borrowers have to turn in their `user` handle *) - [| ∀ th, th ∈ ths ↔ th ∈ dom borrow_map |]. - End with_Qcanon. - - (** Convention: - qi: fraction of the invariant - qP: fraction of P - TODO: still relevant? - *) - - Local Lemma writer_reader_excl g th1 th2 qP : - th1 ≠ th2 -> - reader_locked g th1 qP ** locked g th2 |-- False. - Proof. - iIntros (?) "[H1 H2]". - iDestruct (reader_writer_exclusive with "H1 H2") as %[]; done. - Qed. - - Local Lemma writer_writer_excl g th1 th2 : - locked g th1 ** locked g th2 |-- False. - Proof. - iIntros "[H1 H2]". - iDestruct (writer_writer_exclusive with "H1 H2") as %[]; done. - Qed. - - (** Fractional ownership of a <> guarding the predicate <

>. *) - Definition R γ (q : cQp.t) (P : Qp -> mpred) : Rep := - structR "std::shared_mutex" q ** - (* should also have ownership of the underlying fields *) - pureR (cinv smutex_N γ.(inv_gname) (smutex_inv γ P) ** - cinv_own γ.(inv_gname) q). - #[global] Hint Opaque R : sl_opacity typeclass_instances. - #[only(cfractional,cfracvalid,ascfractional,type_ptr="std::shared_mutex")] derive R. - #[global] Declare Instance R_learnable : forall {σ : genv}, - Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). - - (* TODO prove that we can allocate used_threads ** the invariant in R *) - cpp.spec "std::shared_mutex::shared_mutex()" as ctor_spec with ( - \this this - \pre{P} ▷P 1%Qp - \post Exists g, this |-> R g 1$m P ** used_threads g ∅). - - cpp.spec "std::shared_mutex::~shared_mutex()" as dtor_spec with ( - \this this - (** the "user" set being ∅ enforces that there is no reader or write *) - \pre{g P} this |-> R g 1$m P ** used_threads g ∅ - \post P 1%Qp). - - (* "Inline" version of these specs. *) - cpp.spec "std::shared_mutex::lock()" as lock_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre user g thr - \post P 1%Qp ** locked g thr). - - cpp.spec "std::shared_mutex::try_lock()" as try_lock_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre user g thr - \post{b}[Vbool b] if b then P 1%Qp ** locked g thr else user g thr). - - cpp.spec "std::shared_mutex::unlock()" as unlock_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre locked g thr - \pre ▷ P 1%Qp - \post user g thr). - - cpp.spec "std::shared_mutex::lock_shared()" as lock_shared_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre user g thr - \post ∃ qP, P qP ** reader_locked g thr qP). - - cpp.spec "std::shared_mutex::try_lock_shared()" as try_lock_shared_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre user g thr - \post{b}[Vbool b] if b then ∃ qP, P qP ** reader_locked g thr qP else user g thr). - - cpp.spec "std::shared_mutex::unlock_shared()" as unlock_shared_spec_alt with ( - \this this - \prepost{qi P g} this |-> R g qi P - \persist{thr} current_thread thr - \pre{qP} reader_locked g thr qP - \pre ▷P qP - \post user g thr). - - (** Safety Properties (we model violation of these properties as stuckness): - 1. a thread calling lock() or shared_lock() twice should fail. - This is prevented by having a unique `user`, and turn in user in the inv. - 2. `lock(); ~shared_mutex()` should fail. - Assume we have `used_threads g s`. - dtor requires s=∅, but `lock()` puts the unique `user γ th` in inv, so we - can't deallocate that piece, therefore th ∈ s. - 3. `lock(); unlock_shared()` should fail. - This is prevented because locked can't be transformed into reader_locked, - and writer is not given one. - *) - - Definition do_lock (lk : gname * (Qp -> mpred)) (K: mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - ∃ thr, current_thread thr ∗ user g thr ∗ - (* TODO readd *) - (* ▷ *) - (locked g thr ** P 1%Qp -* K). - #[global] Arguments do_lock /. - - Definition do_unlock (lk : gname * (Qp -> mpred)) (Q : mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - ∃ thr, current_thread thr ** locked g thr ** ▷P 1%Qp ** - (* TODO readd *) - (* ▷ *) - (user g thr -* Q). - #[global] Arguments do_unlock /. - - (* Obtain same specs from (Basic)Lockable. *) - (** <> implements [BasicLockable] *) - Definition T : Type := gname * (Qp -> mpred). - - #[global] Instance shared_mutex_basic_lockable : BasicLockable (T:=T) "std::shared_mutex" (λ q γP, R γP.1 q γP.2) := - { do_lock := fun this => do_lock - ; do_unlock := fun this => do_unlock }. - - cpp.spec "std::shared_mutex::lock()" as lock_spec with - (\exact Reduce (lock_basic_lockable "std::shared_mutex" (λ q γP, R γP.1 q γP.2))). - - cpp.spec "std::shared_mutex::unlock()" as unlock_spec with - (\exact Reduce (unlock_basic_lockable "std::shared_mutex" (λ q γP, R γP.1 q γP.2))). - - Definition do_try_lock (lk : gname * (Qp -> mpred)) (Q : bool -> mpred) : mpred := - let g := lk.1 in - let P := lk.2 in - ∃ thr, current_thread thr ∗ user g thr ∗ - ∀ b : bool, - (if b then P 1%Qp ** locked g thr else user g thr) -∗ Q b. - #[global] Arguments do_try_lock /. - - #[global,program] Instance shared_mutex_lockable : Lockable (T:=T) "std::shared_mutex" (λ q γP, R γP.1 q γP.2) := - { do_try_lock := fun this => do_try_lock }. - - cpp.spec "std::shared_mutex::try_lock()" as try_lock_spec with - (\exact Reduce (try_lock_lockable "std::shared_mutex" (λ q γP, R γP.1 q γP.2))). - - Lemma lock_spec_entails_lock_spec_alt : lock_spec -|- lock_spec_alt. - Proof. - iSplit; iApply specify_mono; ework with br_erefl. - Qed. - - Lemma unlock_spec_entails_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. - Proof. - iSplit; iApply specify_mono; ework with br_erefl. - Qed. - - Lemma try_lock_spec_entails_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. - Proof. - iSplit; iApply specify_mono; ework with br_erefl. - Qed. -End with_cpp. -End shared_mutex. + +(** Predicates for shared mutexes, independent of their implementation. *) +Module Type SHARED_MUTEX_PREDS. + Parameter cpp_ty : type. + Parameter gname : Set. + Parameter G : forall `{Σ : cpp_logic}, Type. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + + Parameter pool_name : gname -> iprop.gname. + Parameter smutex_inv_namespace : gname -> namespace. + #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. + + Parameter token : forall `{Σ : cpp_logic, !G Σ}, + gname -> cQp.t -> mpred. + Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := + token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑smutex_inv_namespace g). + + Parameter locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + ptr -> gname -> thread_idT -> cQp.t -> mpred. + (** [reader_locked this g th q qP] says it contains [token g q] and has + borrowed [P qP] (but [reader_locked] does not include [P] itself). *) + Parameter reader_locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + ptr -> gname -> thread_idT -> cQp.t -> Qp -> mpred. + + (** A shared mutex guarding a fractional predicate [P]. *) + Parameter R : forall `{Σ : cpp_logic, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv}, + gname -> cQp.t -> (Qp -> mpred) -> Rep. + #[global] Hint Opaque token not_locked locked reader_locked R : + sl_opacity typeclass_instances. + + Section with_cpp. + Context `{Σ : cpp_logic, !G Σ}. + + #[global] Declare Instance token_fractional g : CFractional (token g). + #[global] Declare Instance token_timeless g qt : Timeless (token g qt). + + Context {σ : genv}. + + #[global] Instance not_locked_timeless this g th qt : + Timeless (not_locked this g th qt). + Proof. rewrite /not_locked. apply _. Qed. + #[global] Instance not_locked_exclusive this g th : + Exclusive1 (not_locked this g th). + Proof. + intros qt1 qt2. rewrite /not_locked. + iIntros "[_ H1] [_ H2]". + have Hoverlap : ~ ((↑smutex_inv_namespace g : coPset) ## ↑smutex_inv_namespace g). + { have Hnonempty := nclose_non_empty (smutex_inv_namespace g). set_solver. } + iDestruct (MutexSets.my_mutexes_exclusive _ th + (↑smutex_inv_namespace g) (↑smutex_inv_namespace g) Hoverlap + with "[$H1 $H2]") as %[]. + Qed. + #[global] Declare Instance locked_timeless this g th qt : + Timeless (locked this g th qt). + #[global] Declare Instance locked_exclusive this g : + Exclusive2 (locked this g). + #[global] Declare Instance locked_objective this g th qt : + Objective (locked this g th qt). + #[global] Declare Instance reader_locked_timeless this g th qt qP : + Timeless (reader_locked this g th qt qP). + #[global] Declare Instance reader_locked_objective this g th qt qP : + Objective (reader_locked this g th qt qP). + + Axiom reader_writer_exclusive : forall this g th1 th2 qt1 qt2 qP, + th1 <> th2 -> + reader_locked this g th1 qt1 qP -∗ locked this g th2 qt2 -∗ False. + + Context {HAS_THREADS : HasStdThreads Σ}. + + #[only(cfractional,cfracvalid,ascfractional)] derive R. + #[global] Declare Instance R_type_ptr g q P : + Observe (type_ptrR cpp_ty) (R g q P). + End with_cpp. +End SHARED_MUTEX_PREDS. + +(** Reusable contracts and their [BasicLockable]/[Lockable] presentation. *) +Module shared_mutex_spec (Preds : SHARED_MUTEX_PREDS). + Import Preds. + Section with_cpp. + Context `{Σ : cpp_logic, !G Σ}. + Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. + + (* ctor and dtor are the same as mutex.v, except that [P] is fractional. *) + Definition ctor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{pool N P} ▷P 1%Qp + \require Fractional P + \require ∀ q, Objective (P q) + \post Exists g, + [| pool_name g = pool /\ smutex_inv_namespace g = N |] ** + this |-> R g 1$m P ** token g 1$m). + + Definition dtor_spec : ptr -> WpSpec mpred val val := + (\this this + \pre{g P} this |-> R g 1$m P ** token g 1$m + \post P 1%Qp). + + Definition lock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post P 1%Qp ** locked this g thr qt). + + Definition unlock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} locked this g thr qt + \pre ▷P 1%Qp + \post not_locked this g thr qt). + + Definition try_lock_spec_alt : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post{b}[Vbool b] + if b then P 1%Qp ** locked this g thr qt + else not_locked this g thr qt). + + Definition lock_shared_spec : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post ∃ qP, P qP ** reader_locked this g thr qt qP). + + Definition try_lock_shared_spec : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt} not_locked this g thr qt + \post{b}[Vbool b] + if b then ∃ qP, P qP ** reader_locked this g thr qt qP + else not_locked this g thr qt). + + Definition unlock_shared_spec : ptr -> WpSpec mpred val val := + (\this this + \prepost{q P g} this |-> R g q P + \persist{thr} current_thread thr + \pre{qt qP} reader_locked this g thr qt qP + \pre ▷P qP + \post not_locked this g thr qt). + + Definition do_lock (this : ptr) (lk : gname * (Qp -> mpred)) + (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + (locked this lk.1 thr qt ** lk.2 1%Qp -* K). + #[global] Arguments do_lock /. + + Definition do_unlock (this : ptr) (lk : gname * (Qp -> mpred)) + (K : mpred) : mpred := + ∃ thr qt, current_thread thr ** locked this lk.1 thr qt ** ▷lk.2 1%Qp ** + (not_locked this lk.1 thr qt -* K). + #[global] Arguments do_unlock /. + + #[global] Instance shared_mutex_basic_lockable : + BasicLockable (T := gname * (Qp -> mpred)) cpp_ty + (fun q gP => R gP.1 q gP.2) := + { do_lock := do_lock + ; do_unlock := do_unlock }. + + Definition do_try_lock (this : ptr) (lk : gname * (Qp -> mpred)) + (K : bool -> mpred) : mpred := + ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** + ∀ b : bool, + (if b then lk.2 1%Qp ** locked this lk.1 thr qt + else not_locked this lk.1 thr qt) -* K b. + #[global] Arguments do_try_lock /. + + #[global] Instance shared_mutex_lockable : + Lockable (T := gname * (Qp -> mpred)) cpp_ty + (fun q gP => R gP.1 q gP.2) := + { do_try_lock := do_try_lock }. + + Definition lock_spec : ptr -> WpSpec mpred val val := + lock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). + + Definition unlock_spec : ptr -> WpSpec mpred val val := + unlock_basic_lockable cpp_ty (fun q gP => R gP.1 q gP.2). + + Definition try_lock_spec : ptr -> WpSpec mpred val val := + try_lock_lockable cpp_ty (fun q gP => R gP.1 q gP.2). + + Lemma lock_spec_equiv_lock_spec_alt this xs K : + lock_spec this xs K ⊣⊢ lock_spec_alt this xs K. + Proof. + unfold lock_spec, lock_basic_lockable, lock_spec_alt, do_lock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HNL & HK)". + iExists q, (g, P), (P 1%Qp ** locked this g thr qt)%I. + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HNL". + iIntros "[HL HP]". iFrame. + Qed. + + Lemma unlock_spec_equiv_unlock_spec_alt this xs K : + unlock_spec this xs K ⊣⊢ unlock_spec_alt this xs K. + Proof. + unfold unlock_spec, unlock_basic_lockable, unlock_spec_alt, do_unlock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HL & HP & HK)". + iExists q, (g, P), (not_locked this g thr qt). + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HL HP". + iIntros "$". + Qed. + + Lemma try_lock_spec_equiv_try_lock_spec_alt + (Htry_lock : requirements.do_try_lock cpp_ty = do_try_lock) this xs K : + try_lock_spec this xs K ⊣⊢ try_lock_spec_alt this xs K. + Proof. + unfold try_lock_spec, try_lock_lockable. rewrite Htry_lock. + unfold try_lock_spec_alt, do_try_lock. + cbn. iSplit. + - ework with br_erefl. + - iIntros "H". iDestruct "H" as (q P g thr qt) + "(%Hxs & HR & #HT & HNL & HK)". + iExists q, (g, P), (fun b : bool => + if b then (P 1%Qp ** locked this g thr qt)%I + else not_locked this g thr qt). + iFrame "HR HK". iSplit; first done. + iExists thr, qt. iFrame "HT HNL". + iIntros (b) "$". + Qed. + + Section proof_automation. + #[global] Instance R_learn : + Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). + Proof. solve_learnable. Qed. + #[global] Instance not_locked_learn this g th : + LearnEq1 (not_locked this g th). + Proof. solve_learnable. Qed. + #[global] Instance locked_learn this g th : + LearnEq1 (locked this g th). + Proof. solve_learnable. Qed. + #[global] Instance reader_locked_learn this g th : + LearnEq2 (reader_locked this g th). + Proof. solve_learnable. Qed. + End proof_automation. + End with_cpp. +End shared_mutex_spec. + +(** Bind the reusable specifications to their standard C++ names. *) +Module StdSharedMutex + (Preds : SHARED_MUTEX_PREDS with Definition cpp_ty := "std::shared_mutex"%cpp_type). + Include Preds. + #[global] Hint Opaque cpp_ty : sl_opacity. + Module Spec := shared_mutex_spec Preds. + + Section with_cpp. + Context `{Σ : cpp_logic, !G Σ}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MOD : source ⊧ σ}. + + cpp.spec "std::shared_mutex::shared_mutex()" as ctor_spec with + (\exact Reduce Spec.ctor_spec). + cpp.spec "std::shared_mutex::~shared_mutex()" as dtor_spec with + (\exact Reduce Spec.dtor_spec). + cpp.spec "std::shared_mutex::lock()" as lock_spec_alt with + (\exact Reduce Spec.lock_spec_alt). + cpp.spec "std::shared_mutex::try_lock()" as try_lock_spec_alt with + (\exact Reduce Spec.try_lock_spec_alt). + cpp.spec "std::shared_mutex::unlock()" as unlock_spec_alt with + (\exact Reduce Spec.unlock_spec_alt). + cpp.spec "std::shared_mutex::lock_shared()" as lock_shared_spec_alt with + (\exact Reduce Spec.lock_shared_spec). + cpp.spec "std::shared_mutex::try_lock_shared()" as try_lock_shared_spec_alt with + (\exact Reduce Spec.try_lock_shared_spec). + cpp.spec "std::shared_mutex::unlock_shared()" as unlock_shared_spec_alt with + (\exact Reduce Spec.unlock_shared_spec). + cpp.spec "std::shared_mutex::lock()" as lock_spec with + (\exact Reduce Spec.lock_spec). + cpp.spec "std::shared_mutex::unlock()" as unlock_spec with + (\exact Reduce Spec.unlock_spec). + cpp.spec "std::shared_mutex::try_lock()" as try_lock_spec with + (\exact Reduce Spec.try_lock_spec). + + #[global] Instance R_std_type_ptr g q P : + Observe (type_ptrR "std::shared_mutex") (R g q P). + Proof. apply R_type_ptr. Qed. + + Lemma lock_spec_equiv_lock_spec_alt : lock_spec -|- lock_spec_alt. + Proof. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.lock_spec_equiv_lock_spec_alt; done. + Qed. + + Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec -|- unlock_spec_alt. + Proof. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.unlock_spec_equiv_unlock_spec_alt; done. + Qed. + + Lemma try_lock_spec_equiv_try_lock_spec_alt : try_lock_spec -|- try_lock_spec_alt. + Proof. + iSplit; iApply specify_mono; intros this xs K; + rewrite Spec.try_lock_spec_equiv_try_lock_spec_alt; done. + Qed. + End with_cpp. +End StdSharedMutex. + +(** Concrete implementations supply their own [SHARED_MUTEX_PREDS]. *) +Declare Module SharedMutexPreds : SHARED_MUTEX_PREDS + with Definition cpp_ty := "std::shared_mutex"%cpp_type. +Module shared_mutex := StdSharedMutex SharedMutexPreds. From 659a7509b54c4c379c2a6fb83ea1f6a603bcfc25 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 03:23:48 +0000 Subject: [PATCH 083/109] fix shared_mutex/test_cpp_proof.v --- .../test/shared_mutex/test_cpp_proof.v | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v b/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v index 2e4c2d8f..0a357713 100644 --- a/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v @@ -1,30 +1,77 @@ Require Import skylabs.auto.cpp.prelude.proof. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost. -Require Import skylabs.brick.libstdcpp.shared_mutex.shared_mutex. (* XXX *) +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Require Import skylabs.brick.libstdcpp.shared_mutex.shared_mutex. Require Import skylabs.brick.libstdcpp.test.shared_mutex.test_cpp. Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. - Context `{LOCKED : !shared_mutex.lockedG Σ}. + Context `{!shared_mutex.G Σ}. - (* TODO: hacky spec *) - cpp.spec "test_shared_mutex()" as test_mutex_spec from source with ( - (* should be unnecessary *) + cpp.spec "test_shared_mutex()" as test_shared_mutex_spec from source with ( \persist{thr} current_thread thr + \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) \post emp). - Lemma test_mutex_ok : + Lemma test_shared_mutex_ok : verify[source] "test_shared_mutex()". Proof. verify_spec; go. - iExists (const emp); simpl. - iSplit; first work. - iIntros "[% [? H]]". - iMod (lock_ghost.login thr _ empty with "H") as "[??]"; first set_solver. - wuntil Kfree go. - rewrite /Kfree/=. - iMod (lock_ghost.logout thr _ empty with "[$]") as "?"; first set_solver. + iExists pool, (nroot .@@ "test_shared_mutex"), (const emp); go. + rename t into g. + iDestruct (MutexSets.my_mutexes_alloc_mutex_name + (shared_mutex.pool_name g) thr ⊤ + (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + wname [shared_mutex.token] "Htoken". + iAssert (shared_mutex.not_locked m_addr g thr 1$m) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /shared_mutex.not_locked. iFrame. } + iDestruct "Hnotlocked" as "?". + iExists (1$m)%cQp, (g, const emp), + (shared_mutex.locked m_addr g thr 1$m), (1$m)%cQp. go. + iExists (1$m)%cQp, (g, const emp), + (shared_mutex.not_locked m_addr g thr 1$m), (1$m)%cQp. + go. + wname [shared_mutex.not_locked] "Hnotlocked". + iEval (rewrite /shared_mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name + (shared_mutex.pool_name g) thr ⊤ + (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) + with "[Hrest Hname]") as "Hnames"; first iFrame. + go $usenamed=true. + Qed. + + cpp.spec "test_shared_mutex_shared()" as test_mutex_shared_spec from source with ( + \persist{thr} current_thread thr + \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) + \post emp). + + Lemma test_shared_mutex_shared : + verify[source] "test_shared_mutex_shared()". + Proof. + verify_spec; go. + iExists pool, (nroot .@@ "test_shared_mutex_shared"), (const emp); go. + rename t into g. + iDestruct (MutexSets.my_mutexes_alloc_mutex_name + (shared_mutex.pool_name g) thr ⊤ + (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + wname [shared_mutex.token] "Htoken". + iAssert (shared_mutex.not_locked m_addr g thr 1$m) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /shared_mutex.not_locked. iFrame. } + iDestruct "Hnotlocked" as "?". + go. + wname [shared_mutex.not_locked] "Hnotlocked". + iEval (rewrite /shared_mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name + (shared_mutex.pool_name g) thr ⊤ + (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) + with "[Hrest Hname]") as "Hnames"; first iFrame. + go $usenamed=true. Qed. (* TODO prove the rest of the tests *) From a0b1d9d5cf417772533a30d09630ec180c6c3248 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 05:51:06 +0000 Subject: [PATCH 084/109] add shared_mutex custom implementation and proof --- .../test/shared_mutex/custom_shared_mutex.hpp | 35 + .../custom_shared_mutex_hpp_proof.v | 828 ++++++++++++++++++ 2 files changed, 863 insertions(+) create mode 100644 rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp create mode 100644 rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v diff --git a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp new file mode 100644 index 00000000..744f5d33 --- /dev/null +++ b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +// Figure 11 of papers/cpp-mutex/stdcpp.pdf, packaged as a class. +// The saturation check avoids signed overflow in the CAS argument val + 1. +class MySharedMutex { + std::atomic sharers{0}; + +public: + MySharedMutex() {} + ~MySharedMutex() {} + + void lock() { + int idle = 0; + while (!sharers.compare_exchange_strong(idle, -1)) + { idle = 0; } + } + + void unlock() { + sharers = 0; + } + + void lock_shared() { + int val; + do { val = sharers; } + // wait if val=-1 (writer-locked) or val=INT_MAX (so to avoid overflow) + while (val < 0 || val == INT_MAX || + !sharers.compare_exchange_strong(val, val + 1)); + } + + void unlock_shared() { + sharers--; + } +}; diff --git a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v new file mode 100644 index 00000000..0dcf1ca1 --- /dev/null +++ b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v @@ -0,0 +1,828 @@ +Require Import iris.algebra.coPset. +Require Import iris.algebra.lib.gmap_view. +Require Import iris.algebra.agree. +Require Import iris.algebra.frac. +Require Import skylabs.auto.cpp.prelude.proof. +Require Import skylabs.brick.libstdcpp.atomic.spec. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Require Import skylabs.brick.libstdcpp.shared_mutex.shared_mutex. +Require Import skylabs.brick.libstdcpp.test.shared_mutex.custom_shared_mutex_hpp. + +NES.Open std.atomic. + +Import linearity. +#[local] Existing Instance mpred_BiAffine. + +Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. + Definition class_name : name := "MySharedMutex"%cpp_name. + Definition cpp_ty : type := Tnamed class_name. + #[global] Arguments cpp_ty /. + #[global] Arguments class_name /. + + (** The invariant holds the auth piece of [holdersR], and each reader/writer + holds a frac. The map is partial, so only active readers & writers are in + the map. An [entry] of holdersR stores (qt, o_qP), where qt is the + caller's token fraction and o_qP is the optional resource fraction: + (Some qP) represents a reader that borrows [P qP] and [None] represents + a writer. + + [reader_map] encodes the fractions when all threads are readers. + + It might be possible to make [holdersR] a map to reader_map and encode + a writer as having a fraction of [1], but we choose to explicitly make + the writer entry (None) to be distinct with reader entry (Some qP) on + the type level. *) + Definition reader_map := gmap thread_idT (cQp.t * Qp). + Definition entry := (cQp.t * option Qp)%type. + Canonical Structure holdersR : cmra := + gmap_viewR thread_idT (agreeR (leibnizO entry)). + + Record mutex_gname : Set := MkGname { + pool_gname : iprop.gname; + token_gname : iprop.gname; + share_gname : iprop.gname; + holders_gname : iprop.gname; + cinv_gname : iprop.gname; + inv_namespace : namespace; + }. + Definition gname : Set := mutex_gname. + Definition pool_name (g : gname) := g.(pool_gname). + Definition smutex_inv_namespace (g : gname) := g.(inv_namespace). + + Class stateG `{Σ : cpp_logic} := { + #[global] sets_G :: MutexSets.G Σ; + #[local] holders_own :: HasOwn (iPropI _Σ) holdersR; + #[local] holders_upd :: HasOwnUpd (iPropI _Σ) holdersR; + #[local] holders_valid :: HasOwnValid (iPropI _Σ) holdersR; + }. + Definition G := @stateG. + Existing Class G. + #[global] Arguments G {_ _} Σ : assert. + #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. + + Definition token `{Σ : cpp_logic, !G Σ} (g : gname) (q : cQp.t) : mpred := + own g.(token_gname) (q : fracR). + (** [P qP] is always accompanied with [share g qP]: initially the invariant + keeps [share 1 ** P 1]; when a thread borrows [P qP], it also borrows + [share g qP]. This allows to prove writer-writer or writer-reader + exclusivity. *) + Definition share `{Σ : cpp_logic, !G Σ} (g : gname) (q : Qp) : mpred := + own g.(share_gname) (q : fracR). + Definition authority `{Σ : cpp_logic, !G Σ} + (g : gname) (M : gmap thread_idT entry) : mpred := + own g.(holders_gname) + (gmap_view_auth (DfracOwn 1) (to_agree (A := leibnizO entry) <$> M) : holdersR). + Definition holder `{Σ : cpp_logic, !G Σ} + (g : gname) (th : thread_idT) (e : entry) : mpred := + own g.(holders_gname) + (gmap_view_frag th (DfracOwn 1) (to_agree (A := leibnizO entry) e) : holdersR). + + Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := + token g qt ** MutexSets.my_mutexes (pool_name g) th + (CoPset $ ↑smutex_inv_namespace g). + Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := + holder g th (qt, None) ** share g 1. + Definition reader_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) (qP : Qp) : mpred := + holder g th (qt, Some qP) ** share g qP. + + #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} g : + CFractional (token g). + Proof. intros q1 q2. rewrite /token cQp.frac_add -frac_op own_op. done. Qed. + #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} g q : + Timeless (token g q). + Proof. rewrite /token. apply _. Qed. + #[global] Instance share_fractional `{Σ : cpp_logic, !G Σ} g : + Fractional (share g). + Proof. intros q1 q2. rewrite /share -frac_op own_op. done. Qed. + #[global] Instance authority_timeless `{Σ : cpp_logic, !G Σ} g M : + Timeless (authority g M). + Proof. rewrite /authority. apply _. Qed. + #[global] Instance holder_timeless `{Σ : cpp_logic, !G Σ} g th e : + Timeless (holder g th e). + Proof. rewrite /holder. apply _. Qed. + #[global] Instance share_timeless `{Σ : cpp_logic, !G Σ} g q : + Timeless (share g q). + Proof. rewrite /share. apply _. Qed. + + Section ghost_laws. + Context `{Σ : cpp_logic, !G Σ}. + + Lemma full_share_exclusive g q : share g 1 ** share g q |-- False. + Proof. + rewrite /share. iIntros "[A B]". + iDestruct (own_valid_2 with "A B") as %Hvalid. + iPureIntro. move: Hvalid. rewrite frac_op frac_valid. + exact (Qp.not_add_le_l 1 q). + Qed. + + Lemma full_token_exclusive g q : token g 1$m ** token g q |-- False. + Proof. + rewrite /token. iIntros "[A B]". + iDestruct (own_valid_2 with "A B") as %Hvalid. + iPureIntro. move: Hvalid. rewrite frac_op frac_valid. + exact (Qp.not_add_le_l 1 q). + Qed. + + Lemma holder_lookup g M th e : + authority g M ** holder g th e |-- [| M !! th = Some e |]. + Proof. + rewrite /authority /holder. iIntros "[A F]". + iDestruct (own_valid_2 with "A F") as %Hvalid. + apply gmap_view_both_valid in Hvalid as (_ & _ & Heq). + rewrite lookup_fmap in Heq. + destruct (M !! th) as [e'|] eqn:Hlookup; rewrite Hlookup /= in Heq; last inversion Heq. + apply Some_equiv_inj, to_agree_inj, leibniz_equiv in Heq. + subst e'. done. + Qed. + + Lemma holder_insert g M th e : + M !! th = None -> + authority g M |-- (|==> authority g (<[th := e]> M) ** holder g th e). + Proof. + intros Hnone. rewrite /authority /holder fmap_insert. + iIntros "A". iMod (own_update with "A") as "[$ $]"; last done. + apply gmap_view_alloc; [by rewrite lookup_fmap Hnone | done | done]. + Qed. + + Lemma holder_delete g M th e : + authority g M ** holder g th e |-- (|==> authority g (delete th M)). + Proof. + rewrite /authority /holder fmap_delete. iIntros "[A F]". + iMod (own_update_2 with "A F") as "$"; last done. + apply gmap_view_delete. + Qed. + End ghost_laws. + + #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt : Timeless (not_locked this g th qt). + Proof. rewrite /not_locked. apply _. Qed. + #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th : Exclusive1 (not_locked this g th). + Proof. + intros q1 q2. rewrite /not_locked. iIntros "[_ A] [_ B]". + have Hoverlap : ~ ((↑smutex_inv_namespace g : coPset) ## ↑smutex_inv_namespace g). + { have Hne := nclose_non_empty (smutex_inv_namespace g). set_solver. } + iDestruct (MutexSets.my_mutexes_exclusive _ _ _ _ Hoverlap + with "[$A $B]") as %[]. + Qed. + #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt : Timeless (locked this g th qt). + Proof. rewrite /locked. apply _. Qed. + #[global] Instance reader_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt qP : Timeless (reader_locked this g th qt qP). + Proof. rewrite /reader_locked. apply _. Qed. + #[global] Instance locked_objective `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt : Objective (locked this g th qt). + Proof. rewrite /locked /holder /share. apply _. Qed. + #[global] Instance reader_locked_objective `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th qt qP : Objective (reader_locked this g th qt qP). + Proof. rewrite /reader_locked /holder /share. apply _. Qed. + #[global] Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this g : Exclusive2 (locked this g). + Proof. + intros th1 th2 q1 q2. rewrite /locked. iIntros "[_ A] [_ B]". + iDestruct (full_share_exclusive with "[$A $B]") as %[]. + Qed. + Lemma reader_writer_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + this g th1 th2 qt1 qt2 qP : + th1 <> th2 -> + reader_locked this g th1 qt1 qP -∗ locked this g th2 qt2 -∗ False. + Proof. + intros _. rewrite /reader_locked /locked. iIntros "[_ R] [_ W]". + iApply (full_share_exclusive with "[$W $R]"). + Qed. + + (** sums up the fractions in M and add to initializer [s]. + We need [s] because [Qp] does not have 0. *) + Definition total (M : reader_map) (s : Qp) : Qp := + map_fold (fun _ qr acc => (qr.2 + acc)%Qp) s M. + Definition reader_entries (M : reader_map) : gmap thread_idT entry := + (fun qr => (qr.1, Some qr.2)) <$> M. + + Lemma total_empty s : total ∅ s = s. + Proof. by rewrite /total map_fold_empty. Qed. + Lemma total_insert M th qt r s : M !! th = None -> + total (<[th := (qt,r)]> M) s = (r + total M s)%Qp. + Proof. + intros Hnone. rewrite /total map_fold_insert_L; [done | | done]. + intros j1 j2 [qt1 r1] [qt2 r2] acc Hne H1 H2. simpl. + rewrite !Qp.add_assoc (Qp.add_comm r1 r2). done. + Qed. + Lemma total_add M r s : total M (r+s) = (r + total M s)%Qp. + Proof. + induction M as [|th [qt qP] M Hnone IH] using map_ind. + - by rewrite !total_empty. + - rewrite !total_insert // IH !Qp.add_assoc (Qp.add_comm qP r). done. + Qed. + Lemma total_delete M th qt r s : M !! th = Some (qt,r) -> + total (delete th M) (s+r) = total M s. + Proof. + intros Hlookup. rewrite (Qp.add_comm s r) total_add. + have HM : <[th := (qt,r)]> (delete th M) = M. + { rewrite insert_delete decide_True // insert_id //. } + rewrite -{2}HM. + rewrite total_insert; last apply lookup_delete_eq. + done. + Qed. + + Definition max_readers : Z := 2147483647. + #[global] Arguments max_readers /. + Definition deposits `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (M : reader_map) : mpred := + [∗ map] th ↦ qr ∈ M, not_locked this g th qr.1. + + Definition state `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (P : Qp -> mpred) (n : Z) : mpred := + (* thread [th] is the writer *) + (∃ th qt, [| n = -1 |] ** authority g {[th := (qt,None)]} ** + not_locked this g th qt) ∨ + (* 0 or more readers; [share g s ** P s] remains in the invariant *) + (∃ (M : reader_map) (s : Qp), + [| n = Z.of_nat (size M) /\ n <= max_readers /\ total M s = 1%Qp |] ** + authority g (reader_entries M) ** share g s ** P s ** deposits this g M). + + #[global] Instance state_WeaklyObjective + `{Σ : cpp_logic, !G Σ} {σ : genv} this g P n + `{!forall q, Objective (P q)} : WeaklyObjective (state this g P n). + Proof. + rewrite /state /authority /share /deposits /not_locked /token. apply _. + Qed. + + Section state_laws. + Context `{Σ : cpp_logic, !G Σ} {σ : genv}. + + Lemma state_bounds this g P n : + state this g P n |-- [| -1 <= n <= max_readers |]. + Proof. + rewrite /state. iIntros "[W|R]". + - iDestruct "W" as (th qt) "(% & _)". iPureIntro. unfold max_readers. lia. + - iDestruct "R" as (M s) "(% & _)". iPureIntro. lia. + Qed. + + Lemma deposits_fresh this g M th qt : + deposits this g M ** not_locked this g th qt |-- [| M !! th = None |]. + Proof. + rewrite /deposits. iIntros "[D NL]". + destruct (M !! th) as [qr|] eqn:Hl; last done. + iDestruct (big_sepM_lookup _ _ _ _ Hl with "D") as "Dup". + iDestruct (observe_2 False with "NL Dup") as %[]. + Qed. + + Lemma reader_lookup g M th qt qP : + authority g (reader_entries M) ** holder g th (qt,Some qP) |-- + [| M !! th = Some (qt,qP) |]. + Proof. + iIntros "H". iDestruct (holder_lookup with "H") as %Hl. + rewrite /reader_entries lookup_fmap in Hl. + destruct (M !! th) as [[qt' r]|] eqn:He; rewrite He /= in Hl; simplify_eq; done. + Qed. + + Lemma writer_lookup g th0 qt0 th qt : + authority g {[th0 := (qt0,None)]} ** holder g th (qt,None) |-- + [| th0 = th /\ qt0 = qt |]. + Proof. + iIntros "H". iDestruct (holder_lookup with "H") as %Hl. + apply lookup_singleton_Some in Hl. iPureIntro. naive_solver. + Qed. + + Lemma writer_no_reader g th0 qt0 th qt qP : + authority g {[th0 := (qt0,None)]} ** holder g th (qt,Some qP) |-- False. + Proof. + iIntros "H". iDestruct (holder_lookup with "H") as %Hl. + apply lookup_singleton_Some in Hl. naive_solver. + Qed. + + Lemma state_lock this g P qt th : + state this g P 0 ** not_locked this g th qt |-- + (|==> state this g P (-1) ** P 1%Qp ** locked this g th qt). + Proof. + rewrite /state /locked. iIntros "[[W|R] NL]". + - iDestruct "W" as (th0 qt0) "(% & _)". lia. + - iDestruct "R" as (M s) "(%H & A & S & P & D)". + have Hsize : size M = 0%nat by lia. + have Hempty : M = ∅ by apply map_size_empty_iff. + subst M. rewrite total_empty in H. destruct H as (_ & _ & ->). + iEval (rewrite /reader_entries fmap_empty) in "A". + iMod (holder_insert g ∅ th (qt,None) ltac:(done) with "A") as "[A F]". + iEval (rewrite insert_empty) in "A". iModIntro. iFrame "P F S". + iLeft. iExists th, qt. iFrame. done. + Qed. + + Lemma state_unlock this g P n qt th : + state this g P n ** locked this g th qt ** P 1%Qp |-- + (|==> [| n = -1 |] ** state this g P 0 ** not_locked this g th qt). + Proof. + rewrite /state /locked. iIntros "([W|R] & [F S] & P)". + - iDestruct "W" as (th0 qt0) "(%Hn & A & NL)". + iDestruct (writer_lookup with "[$A $F]") as %[-> ->]. + iMod (holder_delete with "[$A $F]") as "A". + iEval (rewrite delete_singleton decide_True //) in "A". iModIntro. iFrame "NL". iSplit; first done. + iRight. iExists ∅, 1%Qp. + rewrite /reader_entries fmap_empty /deposits big_sepM_empty. + iFrame. iPureIntro. rewrite map_size_empty total_empty. unfold max_readers. repeat split; try done; lia. + - iDestruct "R" as (M s) "(_ & _ & S0 & _)". + iDestruct (full_share_exclusive with "[$S $S0]") as %[]. + Qed. + + Lemma state_lock_shared this g P n qt th : + Fractional P -> 0 <= n < max_readers -> + state this g P n ** not_locked this g th qt |-- + (|==> state this g P (n+1) ** ∃ qP, P qP ** reader_locked this g th qt qP). + Proof. + intros HP Hn. rewrite /state /reader_locked. + iIntros "[[W|R] NL]". + - iDestruct "W" as (th0 qt0) "(% & _)". lia. + - iDestruct "R" as (M s) "(%H & A & S & P & D)". + iDestruct (deposits_fresh with "[$D $NL]") as %Hfresh. + iMod (holder_insert g (reader_entries M) th (qt,Some (s/2)%Qp) + with "A") as "[A F]". + { by rewrite /reader_entries lookup_fmap Hfresh. } + iDestruct "S" as "[S1 S2]". + have Hsplit := @fractional _ _ P HP (s/2)%Qp (s/2)%Qp. + rewrite Qp.div_2 in Hsplit. + iEval (rewrite Hsplit) in "P". + iDestruct "P" as "[P1 P2]". + iModIntro. iSplitR "P2 F S2"; last (iExists (s/2)%Qp; iFrame). + iRight. iExists (<[th := (qt,(s/2)%Qp)]> M), (s/2)%Qp. + rewrite /reader_entries fmap_insert /deposits big_sepM_insert //. + iFrame. iPureIntro. rewrite map_size_insert_None //. + destruct H as (Hcount & Hbound & Htotal). + split; first by rewrite Nat2Z.inj_succ -Hcount; lia. + split; first lia. + rewrite total_insert // -total_add Qp.div_2. exact Htotal. + Qed. + + Lemma state_unlock_shared this g P n qt th qP : + Fractional P -> + state this g P n ** reader_locked this g th qt qP ** P qP |-- + (|==> [| 0 < n <= max_readers |] ** + state this g P (n-1) ** not_locked this g th qt). + Proof. + intros HP. rewrite /state /reader_locked. + iIntros "([W|R] & [F S] & P)". + - iDestruct "W" as (th0 qt0) "(_ & A & _)". + iDestruct (writer_no_reader with "[$A $F]") as %[]. + - iDestruct "R" as (M s) "(%H & A & S0 & P0 & D)". + iDestruct (reader_lookup with "[$A $F]") as %Hl. + iMod (holder_delete with "[$A $F]") as "A". + iEval (rewrite /deposits (big_sepM_delete _ _ _ _ Hl)) in "D". + iDestruct "D" as "[NL D]". + have Hsize : (0 < size M)%nat. + { destruct (decide (M = ∅)) as [->|Hne]; first by rewrite lookup_empty in Hl. + have Hsz : size M <> 0%nat by intro Hz; apply map_size_empty_iff in Hz. + lia. } + iModIntro. iFrame "NL". iSplit; first (iPureIntro; lia). + iRight. iExists (delete th M), (s+qP)%Qp. + rewrite /reader_entries fmap_delete /deposits. + rewrite !fractional. iFrame. + iPureIntro. rewrite map_size_delete_Some; last by eauto. + destruct H as (Hcount & Hbound & Htotal). + split. + + rewrite Nat2Z.inj_pred. + * by rewrite -Z.sub_1_r Hcount. + * exact Hsize. + + split; first lia. + rewrite (total_delete M th qt qP s Hl). exact Htotal. + Qed. + + Lemma deposits_full_token_empty this g M : + token g 1$m ** deposits this g M |-- [| M = ∅ |]. + Proof. + induction M as [|th [qt qP] M Hnone IH] using map_ind; first by iIntros "_". + rewrite /deposits big_sepM_insert // /not_locked /=. + iIntros "[T [[T0 _] _]]". + iDestruct (full_token_exclusive with "[$T $T0]") as %[]. + Qed. + + Lemma state_destroy this g P n : + state this g P n ** token g 1$m |-- [| n = 0 |] ** P 1%Qp. + Proof. + rewrite /state. iIntros "[[W|R] T]". + - iDestruct "W" as (th qt) "(_ & _ & NL)". + iEval (rewrite /not_locked) in "NL". iDestruct "NL" as "[T0 _]". + iDestruct (full_token_exclusive with "[$T $T0]") as %[]. + - iDestruct "R" as (M s) "(%H & _ & _ & P & D)". + iDestruct (deposits_full_token_empty with "[$T $D]") as %->. + rewrite map_size_empty total_empty in H. destruct H as (-> & _ & ->). + iFrame. done. + Qed. + End state_laws. + + Definition mutex_inv `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (P : Qp -> mpred) : mpred := + ∃ n : Z, this ,, _field "MySharedMutex::sharers" |-> atomic.R "int" 1$m n ** + state this g P n. + + Definition R `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} + (g : gname) (q : cQp.t) (P : Qp -> mpred) : Rep := + structR class_name q$m ** as_Rep (fun this => + [| Fractional P /\ (forall qP, Objective (P qP)) |] ** + cinv (smutex_inv_namespace g) g.(cinv_gname) (mutex_inv this g P) ** + cinv_own g.(cinv_gname) q). + #[global] Hint Opaque token not_locked locked reader_locked R : + sl_opacity typeclass_instances. + #[only(cfractional,ascfractional,cfracvalid)] derive R. + #[global] Instance R_type_ptr `{Σ : cpp_logic, !G Σ} {σ : genv} + {HAS_THREADS : HasStdThreads Σ} g q P : + Observe (type_ptrR cpp_ty) (R g q P). + Proof. rewrite /R /cpp_ty. apply _. Qed. + + #[local] Instance at_WeaklyObjective `{Σ : cpp_logic} + (p : ptr) (R : Rep) `{!WeaklyObjective (R p)} : + WeaklyObjective (p |-> R). + Proof. rewrite INTERNAL._at_eq. apply _. Qed. + #[global] Instance mutex_inv_WeaklyObjective + `{Σ : cpp_logic, !G Σ} {σ : genv} this g P + `{!forall qP, Objective (P qP)} : WeaklyObjective (mutex_inv this g P). + Proof. rewrite /mutex_inv. apply _. Qed. + + Definition globals `{Σ : cpp_logic} {σ : genv} (q : cQp.t) : mpred := + _global "std::memory_order_seq_cst" |-> + primR "enum std::memory_order" q + (memory_order.to_val memory_order.seq_cst). + #[global] Arguments globals /. + +End CustomSharedMutexPreds. + +Module custom_shared_mutex. + Import CustomSharedMutexPreds. + #[local] Existing Instances holders_own holders_upd holders_valid. + Module Spec := shared_mutex_spec CustomSharedMutexPreds. + Abbreviation IR := CustomSharedMutexPreds.R. + Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). + + Section with_Σ. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, + !CustomSharedMutexPreds.G Σ}. + Context `{MOD : source ⊧ σ}. + + cpp.spec "MySharedMutex::MySharedMutex()" as ctor_spec with + (\exact Reduce Spec.ctor_spec). + cpp.spec "MySharedMutex::~MySharedMutex()" as dtor_spec with + (\exact Reduce Spec.dtor_spec). + cpp.spec "MySharedMutex::lock()" as lock_spec_alt with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.lock_spec_alt this)). + cpp.spec "MySharedMutex::unlock()" as unlock_spec_alt with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.unlock_spec_alt this)). + cpp.spec "MySharedMutex::lock()" as lock_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.lock_spec this)). + cpp.spec "MySharedMutex::unlock()" as unlock_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.unlock_spec this)). + cpp.spec "MySharedMutex::lock_shared()" as lock_shared_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.lock_shared_spec this)). + cpp.spec "MySharedMutex::unlock_shared()" as unlock_shared_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.unlock_shared_spec this)). + + Lemma lock_spec_equiv_lock_spec_alt : lock_spec ⊣⊢ lock_spec_alt. + Proof. + rewrite /lock_spec /lock_spec_alt /specify. + do 2 f_equiv. intros this xs K. rewrite !add_with_equiv. + f_equiv=> qg. by rewrite !add_prepost_equiv Spec.lock_spec_equiv_lock_spec_alt. + Qed. + Lemma unlock_spec_equiv_unlock_spec_alt : unlock_spec ⊣⊢ unlock_spec_alt. + Proof. + rewrite /unlock_spec /unlock_spec_alt /specify. + do 2 f_equiv. intros this xs K. rewrite !add_with_equiv. + f_equiv=> qg. by rewrite !add_prepost_equiv Spec.unlock_spec_equiv_unlock_spec_alt. + Qed. + + Definition bi_later_exist_F := [FWD] @bi.later_exist. + Definition bi_later_sep_F := [FWD] @bi.later_sep. + #[local] Hint Resolve bi_later_exist_F bi_later_sep_F : br_hints. + + #[program] + Definition do_load_C (p : ptr) := + \cancelx + \using denoteModule source + \consuming{g q P} p |-> IR g q P + \proving{K (_ : IsExistential K)} + std.atomic.do_load "int" (BASE (p ,, o_field σ "MySharedMutex::sharers")) K + \instantiate K := (fun n : Z => p |-> IR g q P ** + [| -1 <= n <= max_readers |]) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M IR]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & %HP & #CI & CO)". + rewrite /std.atomic.do_load. + iAcIntro. rewrite /commit_acc. + iInv (smutex_inv_namespace g) as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (n) "(>L & State)". + iDestruct (fupd_mask_subseteq) as ">Y"; [|iModIntro]; first set_solver. + iExists n, (1$m)%cQp. + iSplitL "L"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "L". iMod "Y" as "_". + iDestruct (state_bounds with "State") as %Hbounds. + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists n. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /IR _at_sep _at_as_Rep. + iFrame "CI S CO". auto. + Qed. + #[local] Hint Resolve do_load_C : sl_opacity. + + #[program] + Definition do_writer_cas_C (p expected_p : ptr) := + \cancelx + \using denoteModule source + \consuming{g q P} p |-> IR g q P + \consuming{th qt} not_locked p g th qt + \proving{K (_ : IsExistential K)} + std.atomic.do_compare_exchange "int" false + (BASE (p ,, o_field σ "MySharedMutex::sharers")) expected_p 0%Z (-1)%Z K + \instantiate K := (fun b : bool => + (∃ actual : Z, expected_p |-> intR 1$m actual) ** p |-> IR g q P ** + if b then P 1%Qp ** locked p g th qt else not_locked p g th qt) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M [IR NL]]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & %HP & #CI & CO)". + rewrite /std.atomic.do_compare_exchange. + iAuIntro1. rewrite /atomic1_acc. + iInv (smutex_inv_namespace g) as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (n) "(>L & State)". + iDestruct (fupd_mask_subseteq) as ">Y"; [|iModIntro]; first set_solver. + iExists n. iSplitL "L"; first by ework $usenamed=true with br_erefl. + iSplit. + - iIntros "L". iMod "Y" as "_". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists n. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iFrame. + - iNext. iIntros (b n') "[%Hres L]". iMod "Y" as "_". + destruct Hres as [(-> & -> & ->)|(-> & Hne & ->)]. + + iMod (state_lock with "[$State $NL]") as "(State & P & Locked)". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists (-1)%Z. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iIntros "Expected". + rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO P Locked". + iSplitL "Expected"; first (iExists 0%Z; iFrame). done. + + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists n. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iIntros "Expected". + rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO NL". + iSplitL "Expected"; first (iExists n; iFrame). done. + Qed. + #[local] Hint Resolve do_writer_cas_C : sl_opacity. + + #[program] + Definition do_reader_cas_C (p expected_p : ptr) (expected : Z) := + \cancelx + \using denoteModule source + \consuming{g q P} p |-> IR g q P + \consuming{th qt} not_locked p g th qt + \guard (0 <= expected < max_readers) + \proving{K (_ : IsExistential K)} + std.atomic.do_compare_exchange "int" false + (BASE (p ,, o_field σ "MySharedMutex::sharers")) + expected_p expected (expected+1)%Z K + \instantiate K := (fun b : bool => + (∃ actual : Z, expected_p |-> intR 1$m actual) ** p |-> IR g q P ** + if b then (∃ qP, P qP ** reader_locked p g th qt qP)%I + else not_locked p g th qt) + \end@{mpredI}. + Next Obligation. + intros. have Hrange : 0 <= expected < max_readers by assumption. + iIntros "[#M [IR NL]]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & %HP & #CI & CO)". + rewrite /std.atomic.do_compare_exchange. + iAuIntro1. rewrite /atomic1_acc. + iInv (smutex_inv_namespace g) as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (n) "(>L & State)". + iDestruct (fupd_mask_subseteq) as ">Y"; [|iModIntro]; first set_solver. + iExists n. iSplitL "L"; first by ework $usenamed=true with br_erefl. + iSplit. + - iIntros "L". iMod "Y" as "_". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists n. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iFrame. + - iNext. iIntros (b n') "[%Hres L]". iMod "Y" as "_". + destruct Hres as [(-> & -> & ->)|(-> & Hne & ->)]. + + iMod (state_lock_shared _ _ _ _ _ _ (proj1 HP) Hrange + with "[$State $NL]") as "[State Result]". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists (expected+1)%Z. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iIntros "Expected". + rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO Result". + iSplitL "Expected"; first (iExists expected; iFrame). done. + + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists n. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. iIntros "Expected". + rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO NL". + iSplitL "Expected"; first (iExists n; iFrame). done. + Qed. + #[local] Hint Resolve do_reader_cas_C : sl_opacity. + + #[program] + Definition do_store_C (p : ptr) := + \cancelx + \using denoteModule source + \consuming{g q P} p |-> IR g q P + \consuming{th qt} locked p g th qt + \consuming P 1%Qp + \proving{K (_ : IsExistential K)} + std.atomic.do_store "int" (BASE (p ,, o_field σ "MySharedMutex::sharers")) 0%Z K + \instantiate K := (p |-> IR g q P ** not_locked p g th qt) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M (IR & Locked & P)]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & %HP & #CI & CO)". + rewrite /std.atomic.do_store. + iAcIntro. rewrite /commit_acc. + iInv (smutex_inv_namespace g) as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (n) "(>L & State)". + iDestruct (fupd_mask_subseteq) as ">Y"; [|iModIntro]; first set_solver. + iExists n. iSplitL "L"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "L". iMod "Y" as "_". + iMod (state_unlock with "[$State $Locked $P]") as "(_ & State & NL)". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists 0%Z. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO NL". done. + Qed. + #[local] Hint Resolve do_store_C : sl_opacity. + + #[program] + Definition do_decrement_C (p : ptr) := + \cancelx + \using denoteModule source + \consuming{g q P} p |-> IR g q P + \consuming{th qt qP} reader_locked p g th qt qP + \consuming P qP + \proving{K (_ : IsExistential K)} + (* FIXME std.atomic.do_op is supposed to be local to the atomic module. + Is it fine to use it here directly? *) + std.atomic.do_op "int" (atomic_eval int_rank.Iint Signed Z.add (-1)) + (BASE (p ,, o_field σ "MySharedMutex::sharers")) K + \instantiate K := (fun _ : Z => p |-> IR g q P ** not_locked p g th qt) + \end@{mpredI}. + Next Obligation. + intros. iIntros "[#M (IR & Locked & P)]" (?? ->). + iDestruct (observe [| _ ⊧ _ |] with "M") as "%". + iEval (rewrite /IR _at_sep _at_as_Rep) in "IR". + iDestruct "IR" as "(S & %HP & #CI & CO)". + rewrite /std.atomic.do_op. + iAcIntro. rewrite /commit_acc. + iInv (smutex_inv_namespace g) as "Inv" "Hclose". + iDestruct "Inv" as "[Inv CO]". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (n) "(>L & State)". + iDestruct (fupd_mask_subseteq) as ">Y"; [|iModIntro]; first set_solver. + iExists n. iSplitL "L"; first by ework $usenamed=true with br_erefl. + iNext. iIntros "L". iMod "Y" as "_". + iMod (state_unlock_shared _ _ _ _ _ _ _ (proj1 HP) + with "[$State $Locked $P]") as "(%Hbounds & State & NL)". + have Hop : atomic_eval int_rank.Iint Signed Z.add (-1) n = (n-1)%Z. + { unfold atomic_eval. rewrite to_signed_id; unfold max_readers in *; simpl in *; lia. } + iEval (rewrite Hop) in "L". + iMod ("Hclose" with "[L State]") as "_". + { iNext. rewrite /mutex_inv. iExists (n-1)%Z. + iSplitL "L"; first by ework $usenamed=true with br_erefl. iFrame. } + iModIntro. rewrite /IR _at_sep _at_as_Rep. iFrame "CI S CO NL". done. + Qed. + #[local] Hint Resolve do_decrement_C : sl_opacity. + + Lemma mysharedmutex_lock_alt_proof : verify[source] lock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec; go. + wp_while (fun _ => emp); go. + ework with br_erefl. + go. wp_if; go. + Qed. + + Lemma mysharedmutex_unlock_alt_proof : verify[source] unlock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec; go. + Qed. + + Lemma mysharedmutex_lock_shared_proof : verify[source] lock_shared_spec. + Proof using MOD HAS_THREADS. + verify_spec; go. + wname [_ |-> uninitR _ _] "Val". + iDestruct (_at_mono _ _ (uninitR_anyR "int" 1$m) with "Val") as "?". + wp_do_while (fun _ => emp); go. + repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + all: try (ework with br_erefl; go). + all: repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + all: try match goal with + | Hb : -1 <= ?v |- _ => + have Hrange : 0 <= v < max_readers by unfold max_readers; lia + end. + all: try (ework with br_erefl; go). + all: repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + all: ework with br_erefl. + all: go. + all: repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + all: exfalso; simpl in *; congruence. + Unshelve. all: exact 1%Qp. + Qed. + + Lemma mysharedmutex_unlock_shared_proof : verify[source] unlock_shared_spec. + Proof using MOD HAS_THREADS. + verify_spec; go. + Qed. + + Lemma mysharedmutex_ctor_proof : verify[source] "MySharedMutex::MySharedMutex()". + Proof using MOD HAS_THREADS. + verify_spec. + (* Retain the body while the member constructor initializes the counter; + its update modality permits allocation of the mutex invariant. *) + match goal with |- context[wp ?tu ?rho ?s ?K] => + rewrite (lock (wp tu rho s K)) end. + go. rewrite -lock. iApply fupd_wp. + wname [structR] "S". + wname [P] "P". + wname [_ |-> atomic.R _ _ _] "L". + iMod (own_alloc (1%Qp : fracR)) as (gt) "T"; first done. + iMod (own_alloc (1%Qp : fracR)) as (gs) "Share"; first done. + iMod (own_alloc (gmap_view_auth (DfracOwn 1) ∅ : holdersR)) as (gh) "A". + { apply gmap_view_auth_valid. } + iMod (cinv_alloc ⊤ N + (mutex_inv this (MkGname pool gt gs gh 1%positive N) P) + with "[L P Share A]") as (gi) "[#CI CO]"; last first. + - iModIntro. go. iExists (MkGname pool gt gs gh gi N). + rewrite /IR /R _at_sep _at_as_Rep /token /pool_name /smutex_inv_namespace /=. + iFrame "CI S CO T". auto. + - iNext. rewrite /mutex_inv /state. iExists 0%Z. iFrame "L". + iRight. iExists ∅, 1%Qp. + rewrite /authority /share /reader_entries fmap_empty /deposits big_sepM_empty /=. + iFrame "P Share A". iPureIntro. + rewrite map_size_empty total_empty. unfold max_readers. repeat split; try done; lia. + Qed. + + Lemma mysharedmutex_dtor_proof : verify[source] "MySharedMutex::~MySharedMutex()". + Proof using MOD HAS_THREADS. + verify_spec. + rewrite /IR /R /mutex_inv. + work. + wname [cinv] "#CI". + wname [cinv_own] "CO". + wname [CustomSharedMutexPreds.token] "T". + iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. + go. + iDestruct "Inv" as (n) "[L State]". + iDestruct (state_destroy with "[$State $T]") as "[%Hn P]". + subst n. ego $usenamed=true with br_erefl. + Qed. + + Lemma mysharedmutex_lock_proof : verify[source] lock_spec. + Proof using MOD HAS_THREADS. + rewrite lock_spec_equiv_lock_spec_alt. exact mysharedmutex_lock_alt_proof. + Qed. + Lemma mysharedmutex_unlock_proof : verify[source] unlock_spec. + Proof using MOD HAS_THREADS. + rewrite unlock_spec_equiv_unlock_spec_alt. exact mysharedmutex_unlock_alt_proof. + Qed. + + Definition standard_library_specs : mpred := + std.atomic.ctor "int" ** std.atomic.dtor "int" ** + std.atomic.compare_exchange_strong_1 "int" ** + std.atomic.cast "int" ** std.atomic.assign "int" ** + std.atomic.fetch_unop "int" (op_name := OOMinusMinus). + + Lemma link : + denoteModule source ** standard_library_specs |-- + ctor_spec ** dtor_spec ** lock_spec ** unlock_spec ** + lock_shared_spec ** unlock_shared_spec. + Proof using MOD HAS_THREADS. + rewrite /standard_library_specs. + work. + wapply mysharedmutex_ctor_proof. + wapply mysharedmutex_dtor_proof. + wapply mysharedmutex_lock_proof. + wapply mysharedmutex_unlock_proof. + wapply mysharedmutex_lock_shared_proof. + wapply mysharedmutex_unlock_shared_proof. + work. + Qed. + End with_Σ. +End custom_shared_mutex. From 303d82e235eaa6706acfb873ce996d9f2d83fd3f Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 05:53:47 +0000 Subject: [PATCH 085/109] update dune.inc to include the rocq ast of custom_shared_mutex.hpp and transfer_with_mutex.cpp --- rocq-brick-libstdcpp/test/dune.inc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rocq-brick-libstdcpp/test/dune.inc b/rocq-brick-libstdcpp/test/dune.inc index de81fa8c..6495b838 100644 --- a/rocq-brick-libstdcpp/test/dune.inc +++ b/rocq-brick-libstdcpp/test/dune.inc @@ -359,6 +359,18 @@ (with-stderr-to test_cpp.v.stderr (run cpp2v -v %{input} -o test_cpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) (alias (name srcs) (deps test.cpp)) ) +(subdir mutex + (rule + (targets transfer_with_mutex_cpp.v.stderr transfer_with_mutex_cpp.v) + (alias test_ast) + (deps + (:input transfer_with_mutex.cpp) + (env_var CPP2V_DOCKER_ENABLED) + (glob_files_rec ../*.hpp)) + (action + (with-stderr-to transfer_with_mutex_cpp.v.stderr (run cpp2v -v %{input} -o transfer_with_mutex_cpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) + (alias (name srcs) (deps transfer_with_mutex.cpp)) +) (subdir new (rule (targets demo_cpp.v.stderr demo_cpp.v) @@ -371,6 +383,18 @@ (with-stderr-to demo_cpp.v.stderr (run cpp2v -v %{input} -o demo_cpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) (alias (name srcs) (deps demo.cpp)) ) +(subdir shared_mutex + (rule + (targets custom_shared_mutex_hpp.v.stderr custom_shared_mutex_hpp.v) + (alias test_ast) + (deps + (:input custom_shared_mutex.hpp) + (env_var CPP2V_DOCKER_ENABLED) + (glob_files_rec ../*.hpp)) + (action + (with-stderr-to custom_shared_mutex_hpp.v.stderr (run cpp2v -v %{input} -o custom_shared_mutex_hpp.v --no-elaborate -- -std=c++20 -stdlib=libstdc++ )))) + (alias (name srcs) (deps custom_shared_mutex.hpp)) +) (subdir shared_mutex (rule (targets test_cpp.v.stderr test_cpp.v) From 956e8f114be3ecd9401472b5da19a0f81f42abaf Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 19:17:52 +0000 Subject: [PATCH 086/109] [recursive_mutex.v] move current_thread out of acquireable --- .../proof/mutex/demo_cpp_proof.v | 9 ++- .../proof/mutex/spec/recursive_mutex.v | 72 +++++++++---------- .../proof/mutex/spec/recursive_mutex_atomic.v | 11 ++- .../mutex/custom_recursive_mutex_hpp_proof.v | 12 ++-- .../test/mutex/guard_recursive_cpp_proof.v | 6 +- .../mutex/transfer_with_mutex_cpp_proof.v | 22 +++--- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v index a8d6d7b0..fbe41b18 100644 --- a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v +++ b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v @@ -74,14 +74,16 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \persist{th} current_thread th + \pre{args qt} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) b) args) (P this)). cpp.spec "C::update_b(long)" as C_update_b from demo_cpp.source with (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \persist{th} current_thread th + \pre{args qt} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk a (trim 64 (b + x))) args) (P this)). #[global] Instance CR_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) CR). @@ -142,7 +144,8 @@ Section with_cpp. (\this this \arg{x} "x" (Vint x) \prepost{γ q} this |-> CR γ q - \pre{args qt th} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) + \persist{th} current_thread th + \pre{args qt} std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th args (TT:=TT) (P this) \post std_recursive_mutex.acquireable (this ,, _field "C::mut") γ qt th (TT:=TT) (std_recursive_mutex.update (TT:=TT) (fun (a b : Z) => mk (trim 64 (a+x)) (trim 64 (b-x))) args) (P this)). Lemma transfer_ok : verify[source] "C::transfer(int)". diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index fe14b9ee..08fc9a94 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -147,39 +147,42 @@ End RECURSIVE_MUTEX_PREDS. Module recursive_mutex_spec (T : MutexCPPName) (Preds : RECURSIVE_MUTEX_PREDS T). Import Preds RecursiveMutexState. + + (** Ownership of a thread's acquisition state. Operations separately require + [current_thread th] to establish that the caller is that thread. *) + sl.lock + Definition acquireable `{Σ : cpp_logic, !G Σ} {σ : genv} + (this : ptr) (g : gname) (qt : cQp.t) + (th : thread_idT) {TT : tele} (s : acquire_state TT) + (P : TT -t> mpred) : mpred := + match s with + | NotHeld => not_locked this g qt th + | Held n xs => locked this g qt th n ** @tele_app TT (fun _ => mpred) P xs + end. + #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. + Section with_cpp. Context `{Σ : cpp_logic, !G Σ}. Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. - Definition acquireable (this : ptr) (g : gname) (qt : cQp.t) - (th : thread_idT) {TT : tele} (s : acquire_state TT) - (P : TT -t> mpred) : mpred := - current_thread th ** - match s with - | NotHeld => not_locked this g qt th - | Held n xs => locked this g qt th n ** @tele_app TT (fun _ => mpred) P xs - end. - #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. - - #[global] Instance acquireable_current_thread this g qt th TT s P : - Observe (current_thread th) (acquireable this g qt th (TT := TT) s P). - Proof. rewrite /acquireable. apply _. Qed. - Definition lock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{qt th n} acquireable this g qt th n P + \persist{th} current_thread th + \pre{qt n} acquireable this g qt th n P \post Exists n', [| acquire n n' |] ** ▷ acquireable this g qt th n' P). Definition unlock_spec : ptr -> WpSpec mpred val val := (\this this \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) - \pre{qt th n args} acquireable this g qt th (Held n args) P + \persist{th} current_thread th + \pre{qt n args} acquireable this g qt th (Held n args) P \post acquireable this g qt th (release $ Held n args) P). Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ TT P qt th n, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** + current_thread th ** acquireable this lk.1 qt th n P ** ((* TODO readd *) (* ▷ *) @@ -189,6 +192,7 @@ Module recursive_mutex_spec (T : MutexCPPName) Definition do_unlock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ TT P qt th n args, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** + current_thread th ** acquireable this lk.1 qt th (Held n args) P ** ((* TODO readd *) (* ▷ *) @@ -210,18 +214,18 @@ Module recursive_mutex_spec (T : MutexCPPName) Proof. unfold lock_spec, lock_spec_alt, lock_basic_lockable, do_lock. cbn. iSplit. - - iIntros "H". iDestruct "H" as (g TT P q qt th n) - "(%Hxs & HR & HA & HK)". + - iIntros "H". iDestruct "H" as (g TT P q th qt n) + "(%Hxs & HR & #Hth & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), (Exists n', [| acquire n n' |] ** ▷ acquireable this g qt th n' P)%I. iFrame "HR HK". iSplit; first done. - iExists TT, P, qt, th, n. iFrame "HA". + iExists TT, P, qt, th, n. iFrame "Hth HA". iSplit; first done. iIntros "$". - iIntros "H". iDestruct "H" as (q [g J] K') "(%Hxs & HR & Hdo & HK)". - iDestruct "Hdo" as (TT P qt th n) "(%Heq & HA & Hcont)". + iDestruct "Hdo" as (TT P qt th n) "(%Heq & #Hth & HA & Hcont)". simpl in *. subst J. - iExists g, TT, P, q, qt, th, n. iFrame "HR HA". + iExists g, TT, P, q, th, qt, n. iFrame "HR Hth HA". iSplit; first done. iIntros "[HR Hac]". iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. @@ -231,18 +235,18 @@ Module recursive_mutex_spec (T : MutexCPPName) Proof. unfold unlock_spec, unlock_spec_alt, unlock_basic_lockable, do_unlock. cbn. iSplit. - - iIntros "H". iDestruct "H" as (g TT P q qt th n args) - "(%Hxs & HR & HA & HK)". + - iIntros "H". iDestruct "H" as (g TT P q th qt n args) + "(%Hxs & HR & #Hth & HA & HK)". iExists q, (g, (∃ xs : tele_arg TT, @tele_app TT (fun _ => mpred) P xs)%I), (acquireable this g qt th (release $ Held n args) P). iFrame "HR HK". iSplit; first done. - iExists TT, P, qt, th, n, args. iFrame "HA". + iExists TT, P, qt, th, n, args. iFrame "Hth HA". iSplit; first done. iIntros "$". - iIntros "H". iDestruct "H" as (q [g J] K') "(%Hxs & HR & Hdo & HK)". - iDestruct "Hdo" as (TT P qt th n args) "(%Heq & HA & Hcont)". + iDestruct "Hdo" as (TT P qt th n args) "(%Heq & #Hth & HA & Hcont)". simpl in *. subst J. - iExists g, TT, P, q, qt, th, n, args. iFrame "HR HA". + iExists g, TT, P, q, th, qt, n, args. iFrame "HR Hth HA". iSplit; first done. iIntros "[HR Hac]". iApply "HK". iFrame "HR". iApply "Hcont". iExact "Hac". Qed. @@ -328,9 +332,6 @@ Module recursive_mutex_spec (T : MutexCPPName) [qt' = qt; n' = n] }. Proof. solve_learnable. Qed. - Definition acquireable_current_thread_F := - ltac:(mk_obs_fwd acquireable_current_thread). - #[program] Definition acquireable_is_acquired_C {TT} this g qt th t t' P (_ : acquire (TT := TT) t t') := @@ -342,7 +343,7 @@ Module recursive_mutex_spec (T : MutexCPPName) \end. Next Obligation. intros * (? & ? & -> & ->)%is_held. - rewrite /acquireable /=. ego. + rewrite acquireable.unlock /=. ego. Qed. #[program] @@ -358,27 +359,26 @@ Module recursive_mutex_spec (T : MutexCPPName) \deduce @tele_app TT (fun _ => mpred) P args \through @tele_app TT (fun _ => mpred) P' args \end. - Next Obligation. intros. rewrite /acquireable /=; work. Qed. + Next Obligation. intros. rewrite acquireable.unlock /=; work. Qed. #[program] Definition own_P_is_acquireable_C {TT} this g qt n P := \cancelx - \preserving{th} current_thread th - \consuming locked this g qt th n + \consuming{th} locked this g qt th n \bound n' args \proving acquireable this (TT := TT) g qt th (Held n' args) P \through @tele_app TT (fun _ => mpred) P args \through [| n' = n |] \end. Next Obligation. - intros. iIntros "[#Hth Htoken]" (n' args) "[HP %Heq]". - subst n'. rewrite /acquireable /=. iFrame "#∗". + intros. iIntros "Htoken" (n' args) "[HP %Heq]". + subst n'. rewrite acquireable.unlock /=. iFrame "#∗". Qed. End proof_automation. End with_cpp. #[global] Hint Resolve acquireable_acquireable_C acquireable_is_acquired_C - own_P_is_acquireable_C acquireable_current_thread_F : br_hints. + own_P_is_acquireable_C : br_hints. End recursive_mutex_spec. Module StdRecursiveMutexName. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index 98507443..73c87cb3 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -442,7 +442,6 @@ Section with_cpp. work. iModIntro; work. iExists pool, N. work. - rewrite /acquireable /=. (* The owner slot is unused while the initial count is zero. *) set th : thread_idT := inhabitant. iMod (own_alloc (●E (O, th, (1$m)%cQp) ⋅ ◯E (O, th, (1$m)%cQp))) as (g) "(? & ?)". @@ -493,7 +492,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** (∃ t, [| acquire n t |] ∗ ▷ acquireable this g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock /acquireable /not_locked /locked /given_token + rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token /token /pool_name /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". @@ -514,12 +513,12 @@ Section with_cpp. first apply (excl_auth_update _ _ (1, th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). rewrite /acquireable /locked /given_token /=. work. + iExists (Held 0 args). rewrite /locked /given_token /=. work. - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; first apply (excl_auth_update _ _ (S (S n), th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). rewrite /acquireable /locked /given_token /=. work. + iExists (Held (S n) args). rewrite /locked /given_token /=. work. Qed. Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : @@ -535,7 +534,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** acquireable this g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock /acquireable /not_locked /locked /given_token + rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token /token /pool_name /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". @@ -546,7 +545,7 @@ Section with_cpp. iMod "Hclose'" as "_". iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; first apply (excl_auth_update _ _ (n, th, qt)). - rewrite /acquireable /not_locked /locked /given_token /token + rewrite /not_locked /locked /given_token /token /pool_name /rmutex_inv_namespace release.unlock; destruct n. 1: rewrite [in at_specs.locked _ _ _ 0]at_specs.locked.unlock /=. 1: iDestruct select (MutexSets.my_mutexes _ _ _ ** at_specs.token _ _) as "[? ?]". diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index afe26f30..08be183f 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -564,7 +564,7 @@ Module custom_recursive_mutex. Lemma lock_proof : verify[source] lock_spec. Proof using MOD. verify_spec. - rewrite /acquireable. destruct n as [|n xs]. + rewrite Spec.acquireable.unlock. destruct n as [|n xs]. (* First acquisition: exclude our own ID, then acquire the inner mutex. *) - ego. (* Publishing ownership transfers the inner held capability into the @@ -576,14 +576,14 @@ Module custom_recursive_mutex. iDestruct (store_owner_action with "[$HR $Hfrag $Hinner]") as "Hstore". iSplitL "Hstore"; first iExact "Hstore". ego. rewrite /countR. ego. - rewrite /acquireable /locked /countR. ego. + rewrite Spec.acquireable.unlock /locked /countR. ego. wname [P] "HP". iExact "HP". (* Recursive acquisition: the owner fragment identifies this thread. *) - rewrite /locked /countR. ego. destruct (decide (Z.of_nat (S n) = 18446744073709551615)%Z) as [Hmax|Hmax]; go. (* At the counter limit, the loop has no returning execution. *) + rewrite Hmax. go. wp_for (fun _ => emp); go. - + ego. rewrite /acquireable /locked /countR. + + ego. rewrite Spec.acquireable.unlock /locked /countR. have Hcount : (Z.of_nat (S n) + 1)%Z = Z.of_nat (S (S n)) by lia. rewrite Hcount. iFrame "#∗". Qed. @@ -591,7 +591,7 @@ Module custom_recursive_mutex. Lemma unlock_proof : verify[source] unlock_spec. Proof using MOD. verify_spec. - rewrite /acquireable /locked /countR. + rewrite Spec.acquireable.unlock /locked /countR. go. destruct n as [|n]. (* Final release: clear the owner to recover the inner held capability; @@ -605,9 +605,9 @@ Module custom_recursive_mutex. iSplitL "P Count OF". { iNext. rewrite /protected /countR /owner_frag. iFrame "Count OF". iExists args. iExact "P". } - ego. rewrite /acquireable release.unlock. iFrame "#∗". + ego. rewrite Spec.acquireable.unlock release.unlock. iFrame "#∗". (* Nested release: decrement the count and keep the resource. *) - - ego. rewrite /acquireable release.unlock /locked /countR. + - ego. rewrite Spec.acquireable.unlock release.unlock /locked /countR. have Hcount : (Z.of_nat (S (S n)) - 1)%Z = Z.of_nat (S n) by lia. rewrite Hcount. iFrame "#∗". Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index 03ac957f..cb03062d 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -178,7 +178,7 @@ Section with_cpp. iAssert (std_recursive_mutex.acquireable (c_addr ,, _field "C::m") (TT := TT) t 1$m thr std_recursive_mutex.NotHeld (P c_addr))%I with "[Htoken Hname]" as "Hacquireable". - { rewrite /std_recursive_mutex.acquireable /= + { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. iFrame "#∗". } iDestruct "Hacquireable" as "?". go. destruct args as [a []]. @@ -216,9 +216,9 @@ Section with_cpp. rewrite P.unlock CR'.unlock; go. match goal with g : std_recursive_mutex.gname |- _ => rename g into γ end. iDestruct select (std_recursive_mutex.acquireable _ _ _ _ _ _) as "Hacquireable". - iEval (rewrite /std_recursive_mutex.acquireable /= + iEval (rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked) in "Hacquireable". - iDestruct "Hacquireable" as "(_ & Htoken & Hname)". + iDestruct "Hacquireable" as "[Htoken Hname]". iDestruct (MutexSets.my_mutexes_join_mutex_name (std_recursive_mutex.pool_name γ) thr ⊤ (↑std_recursive_mutex.rmutex_inv_namespace γ) ltac:(set_solver) diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index dff652b2..fa321438 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -224,7 +224,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Htoken Hnames]" as "Hacquireable". - { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. + { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "#∗". } iExists f, g, q, (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th @@ -234,13 +234,13 @@ Section class_C. - rewrite /std_recursive_mutex.do_lock /=. iExists balance_args, (fun n : Z => this |-> account_balanceR n)%I, q, th, std_recursive_mutex.NotHeld. - iSplit; first done. iFrame "Hacquireable". + iSplit; first done. iFrame "Hth Hacquireable". iIntros "Hlocked". iDestruct "Hlocked" as (s) "[%Hac Hlocked]". rewrite std_recursive_mutex.acquire.unlock in Hac. destruct Hac as [[v []] ->]. - iEval (rewrite /std_recursive_mutex.acquireable /=) in "Hlocked". - iDestruct "Hlocked" as "(_ & Hheld & Hbalance)". + iEval (rewrite std_recursive_mutex.acquireable.unlock /=) in "Hlocked". + iDestruct "Hlocked" as "[Hheld Hbalance]". iMod "Hheld". iMod "Hbalance". iModIntro. rewrite /balance_callback. iIntros (Q) "HQ". iSpecialize ("Hcallback" $! v with "Hbalance"). @@ -250,16 +250,16 @@ Section class_C. iApply "HQ". iExists balance_args, (fun n : Z => this |-> account_balanceR n)%I, q, th, 0%nat, (balance_arg v'). - iSplit; first done. iSplitL "Hheld Hbalance". - { rewrite /std_recursive_mutex.acquireable /=. iFrame "#∗". } + iSplit; first done. iFrame "Hth". iSplitL "Hheld Hbalance". + { rewrite std_recursive_mutex.acquireable.unlock /=. iFrame "#∗". } iIntros "Hunlocked". iEval (rewrite std_recursive_mutex.release.unlock) in "Hunlocked". iFrame. - iIntros "(HR & Hacquireable & HK)". iApply "Hpost". iFrame "HK". - iEval (rewrite /std_recursive_mutex.acquireable /= + iEval (rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked) in "Hacquireable". - iDestruct "Hacquireable" as "(_ & Htoken & Hnames)". + iDestruct "Hacquireable" as "[Htoken Hnames]". destruct Hg as [Hgpool HN]. iEval (rewrite Hgpool HN) in "Hnames". iFrame "Hnames". rewrite accountR.unlock _at_exists. iExists g. @@ -306,7 +306,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Hfrom_names Hfrom_token]" as "?". - { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. iFrame "#∗". } + { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. iFrame "#∗". } iDestruct select (MutexSets.my_mutexes _ th (coPset.CoPset (↑to_namespace))) as "Hto_names". iEval (rewrite -a -b0) in "Hto_names". @@ -314,7 +314,7 @@ Section class_C. iAssert (std_recursive_mutex.acquireable (to ,, _field "C::mut") (TT := balance_args) g qto th std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I with "[Hto_names Hto_token]" as "?". - { rewrite /std_recursive_mutex.acquireable /= /std_recursive_mutex.not_locked. iFrame "#∗". } + { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. iFrame "#∗". } go. rewrite std_recursive_mutex.acquire.unlock in H1, H2. destruct H1 as [[from_balance []] Hfrom]. inversion Hfrom; subst. @@ -330,7 +330,7 @@ Section class_C. (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I. rewrite std_recursive_mutex.release.unlock /=. go. - rewrite accountR.unlock /std_recursive_mutex.acquireable /= + rewrite accountR.unlock std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. go with br_erefl. rewrite a b b0. go. Qed. From 72aa9163da9181bf5f1a066c22df54e56e1839ba Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 21:18:20 +0000 Subject: [PATCH 087/109] remove the MUTEX_SET module type, as it has exactly one implementation that we need --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 91 +------------------- 1 file changed, 1 insertion(+), 90 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index f0cd4e43..ad86724b 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -14,93 +14,6 @@ Import linearity. (** Various ghost state constructions and laws for concurrency library specs and proofs. *) -Module Type MUTEX_SETS. - Parameter cmraR : cmra. - - Class G `{Σ : cpp_logic} := { - #[local] has_own :: HasOwn (iPropI _Σ) cmraR; - #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; - #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; - }. - #[global] Arguments G {_ _} Σ : assert. - - Parameter mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> gset thread_idT -> mpred. - Parameter my_mutexes : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> thread_idT -> coPset.coPset_disj -> mpred. - - #[global] Declare Instance mutex_set_map_timeless - `{Σ : cpp_logic, !G Σ} γ M : Timeless (mutex_set_map γ M). - #[global] Declare Instance my_mutexes_timeless - `{Σ : cpp_logic, !G Σ} γ th E : Timeless (my_mutexes γ th E). - - #[global] Declare Instance mutex_set_map_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ M : WeaklyObjective (mutex_set_map γ M). - #[global] Declare Instance my_mutexes_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ th E : WeaklyObjective (my_mutexes γ th E). - #[global] Declare Instance my_mutexes_objective - `{Σ : cpp_logic, !G Σ} γ th E : Objective (my_mutexes γ th E). - - Axiom my_mutexes_exclusive : forall `{Σ : cpp_logic, !G Σ} γ th (E1 E2: coPset), - ~(E1 ## E2) -> - my_mutexes γ th (CoPset E1) ** - my_mutexes γ th (CoPset E2) |-- False. - Axiom alloc_mutex_set_map : forall `{Σ : cpp_logic, !G Σ}, - ⊢ |==> ∃ γ, mutex_set_map γ ∅. - Axiom mutex_sets_alloc_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, - th ∉ T -> - mutex_set_map γ T |-- - (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (CoPset ⊤)). - Axiom mutex_sets_free_thread : forall `{Σ : cpp_logic, !G Σ} γ T th, - th ∈ T -> - mutex_set_map γ T ** my_mutexes γ th (CoPset ⊤) |-- - mutex_set_map γ (T \ {[th]}). - Axiom mutex_set_map_empty_no_my_mutexes : - forall `{Σ : cpp_logic, !G Σ} γ th (N : namespace), - mutex_set_map γ ∅ ** my_mutexes γ th (CoPset (↑N)) |-- False. - Axiom my_mutexes_alloc_mutex_name : forall `{Σ : cpp_logic, !G Σ} - γ th (E N : coPset), - N ⊆ E -> - my_mutexes γ th (CoPset E) |-- - my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). - Axiom my_mutexes_split : forall `{Σ : cpp_logic, !G Σ} - γ th (E1 E2 : coPset), - E1 ## E2 -> - my_mutexes γ th (CoPset (E1 ∪ E2)) ⊣⊢ - my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2). - Axiom my_mutexes_join_mutex_name : forall `{Σ : cpp_logic, !G Σ} - γ th (E N : coPset), - N ⊆ E -> - my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N) |-- - my_mutexes γ th (CoPset E). - - (* an example that two threads can allocate the same namespace token. *) - Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !G Σ} - (th1 th2 : thread_idT) (N : coPset), - th1 ≠ th2 -> - ⊢ |==> ∃ γ, - my_mutexes γ th1 (CoPset N) ** - my_mutexes γ th2 (CoPset N) ** - (mutex_set_map γ {[th1; th2]} ** - my_mutexes γ th1 (CoPset (⊤ \ N)) ** - my_mutexes γ th2 (CoPset (⊤ \ N))). - Proof. - intros until N. intros Hneq. - iMod alloc_mutex_set_map as (γ) "Hmap". - iMod (mutex_sets_alloc_thread γ ∅ th1 ltac:(set_solver) - with "Hmap") as "[Hmap Ht1]". - iEval (rewrite left_id_L) in "Hmap". - iMod (mutex_sets_alloc_thread γ {[th1]} th2 ltac:(set_solver) - with "Hmap") as "[Hmap Ht2]". - iDestruct (my_mutexes_alloc_mutex_name γ th1 ⊤ N ltac:(set_solver) - with "Ht1") as "[Hr1 Hn1]". - iDestruct (my_mutexes_alloc_mutex_name γ th2 ⊤ N ltac:(set_solver) - with "Ht2") as "[Hr2 Hn2]". - iModIntro. iExists γ. iFrame. - Qed. -End MUTEX_SETS. - Module Type MUTEX_TOKENS. Parameter cmraR : cmra. @@ -180,9 +93,7 @@ Module Type OWNER_TID. (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). End OWNER_TID. -(* Proofs that the ghost state modules are inhabited. *) - -Module MutexSets : MUTEX_SETS. +Module MutexSets. Canonical Structure cmraR : cmra := thread_idT -d> coPset_disjR. From 7c0c5e8a320988cf84ae6b75d6b0943834582c36 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Mon, 12 Oct 2026 21:32:51 +0000 Subject: [PATCH 088/109] fix custom_mutex_hpp_proof.v --- rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 80084c7a..452e765a 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -131,7 +131,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. iApply MutexTokens.token_full_init. iExact "GT". Qed. - Lemma state_lock {σ : genv} this γ th q : + Lemma state_lock {σ : genv} this (γ : gname) th (q : cQp.t) : state γ false ** not_locked this γ th q |-- (|==> state γ true ** owner_token γ th q). Proof. @@ -147,7 +147,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. iModIntro. iFrame "GT OF Held". iExists th. iFrame. Qed. - Lemma state_unlock {σ : genv} this γ th q : + Lemma state_unlock {σ : genv} this (γ : gname) th (q : cQp.t) : state γ true ** owner_token γ th q |-- (|==> state γ false ** not_locked this γ th q). Proof. @@ -383,7 +383,7 @@ Module custom_mutex. \prepost{g q P} this |-> IR g q P \persist{thr} current_thread thr \prepost{qg} globals qg - \pre{qt} this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** + \pre{(qt : cQp.t)} this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** owner_token g thr qt \pre ▷P \post not_locked this g thr qt). @@ -498,7 +498,7 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id - \consuming{qt} owner_token g thr qt + \consuming{(qt : cQp.t)} owner_token g thr qt \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K \instantiate K := (p |-> IR g q P ** not_locked p g thr qt) From 6af57fd398eafc315239e6c36df668227112511d Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 13 Oct 2026 01:47:09 +0000 Subject: [PATCH 089/109] gpool, the ghost name for my_mutexes and mutex_set_map, is moved into an instance mutex_set_G, following the invariant_name pattern in wsat.v in iris --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 163 ++++++++++-------- .../proof/mutex/demo_cpp_proof.v | 5 +- .../proof/mutex/proof/unique_lock.v | 1 + .../mutex/proof/unique_lock_recursive_mutex.v | 1 + rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 42 ++--- .../proof/mutex/spec/recursive_mutex.v | 30 ++-- .../proof/mutex/spec/recursive_mutex_atomic.v | 52 +++--- .../proof/mutex/spec/scoped_lock.v | 2 + .../proof/shared_mutex/shared_mutex.v | 27 +-- rocq-brick-libstdcpp/proof/thread/spec.v | 20 ++- .../test/mutex/custom_mutex_hpp_proof.v | 79 ++++----- .../mutex/custom_recursive_mutex_hpp_proof.v | 28 ++- .../test/mutex/double_incr_cpp_proof.v | 26 +-- .../mutex/transfer_with_mutex_cpp_proof.v | 115 ++++++------ .../custom_shared_mutex_hpp_proof.v | 42 ++--- .../test/shared_mutex/test_cpp_proof.v | 17 +- 16 files changed, 349 insertions(+), 301 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index ad86724b..9751a7de 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -93,53 +93,76 @@ Module Type OWNER_TID. (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). End OWNER_TID. -Module MutexSets. +Module mutex_set_G. Canonical Structure cmraR : cmra := thread_idT -d> coPset_disjR. - Class G `{Σ : cpp_logic} := { + Class mutex_set_preG `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; }. - #[global] Arguments G {_ _} Σ : assert. + #[global] Arguments mutex_set_preG {_ _} Σ : assert. + + (* Users of a mutex_set_map ghost state should assume a single instance for + the entire C++ program. This can be done by using a single instance of + [mutex_set_G] to fix [mutex_sets_name]. *) + Class mutex_set_G `{Σ : cpp_logic} := { + #[global] pre_G :: mutex_set_preG Σ; + mutex_sets_name : iprop.gname; + }. + #[global] Arguments mutex_set_G {_ _} Σ : assert. +End mutex_set_G. - Definition my_mutexes `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (th : thread_idT) (E : coPset_disj) : mpred := - own γ (discrete_fun_singleton th E : cmraR). +Module MutexSets. + Import mutex_set_G. + #[local] Existing Instances has_own has_upd has_valid. + + Definition my_mutexes `{Σ : cpp_logic, !mutex_set_G Σ} + (th : thread_idT) (E : coPset_disj) : mpred := + own mutex_sets_name (discrete_fun_singleton th E : cmraR). (** The pool reserves the full set for each thread not yet registered. *) Definition reserve (M : gset thread_idT) : cmraR := fun th => if decide (th ∈ M) then ε else CoPset ⊤. - Definition mutex_set_map `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (M : gset thread_idT) : mpred := - own γ (reserve M). - - #[global] Instance mutex_set_map_timeless `{Σ : cpp_logic, !G Σ} γ M : - Timeless (mutex_set_map γ M). - Proof. rewrite /mutex_set_map. apply _. Qed. - #[global] Instance my_mutexes_timeless `{Σ : cpp_logic, !G Σ} γ th E : - Timeless (my_mutexes γ th E). - Proof. rewrite /my_mutexes. apply _. Qed. - - #[global] Instance mutex_set_map_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ M : - WeaklyObjective (mutex_set_map γ M). - Proof. rewrite /mutex_set_map. apply _. Qed. - #[global] Instance my_mutexes_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ th E : - WeaklyObjective (my_mutexes γ th E). - Proof. rewrite /my_mutexes. apply _. Qed. - #[global] Instance my_mutexes_objective `{Σ : cpp_logic, !G Σ} γ th E : - Objective (my_mutexes γ th E). - Proof. rewrite /my_mutexes. apply _. Qed. + Definition mutex_set_map `{Σ : cpp_logic, !mutex_set_G Σ} + (M : gset thread_idT) : mpred := + own mutex_sets_name (reserve M). - Section theory. - Context `{Σ : cpp_logic, !G Σ}. + Lemma alloc_mutex_set_map `{Σ : cpp_logic, !mutex_set_preG Σ} : + ⊢ |==> ∃ _ : mutex_set_G Σ, mutex_set_map ∅. + Proof. + iMod (own_alloc (reserve ∅)) as (γ) "Hmap". + { intros th. rewrite /reserve. case_decide; done. } + iModIntro. iExists {| pre_G := _; mutex_sets_name := γ |}. + iExact "Hmap". + Qed. - Lemma my_mutexes_exclusive γ th (E1 E2 : coPset) : + Section theory. + Context `{Σ : cpp_logic, !mutex_set_G Σ}. + + #[global] Instance mutex_set_map_timeless M : + Timeless (mutex_set_map M). + Proof. rewrite /mutex_set_map. apply _. Qed. + #[global] Instance my_mutexes_timeless th E : + Timeless (my_mutexes th E). + Proof. rewrite /my_mutexes. apply _. Qed. + + #[global] Instance mutex_set_map_WeaklyObjective M : + WeaklyObjective (mutex_set_map M). + Proof. rewrite /mutex_set_map. apply _. Qed. + #[global] Instance my_mutexes_WeaklyObjective th E : + WeaklyObjective (my_mutexes th E). + Proof. rewrite /my_mutexes. apply _. Qed. + #[global] Instance my_mutexes_objective th E : + Objective (my_mutexes th E). + Proof. rewrite /my_mutexes. apply _. Qed. + + Lemma my_mutexes_exclusive th (E1 E2 : coPset) : ~(E1 ## E2) -> - my_mutexes γ th (CoPset E1) ** - my_mutexes γ th (CoPset E2) |-- False. + my_mutexes th (CoPset E1) ** + my_mutexes th (CoPset E2) |-- False. Proof. rewrite /my_mutexes. iIntros (Hoverlap) "[H1 H2]". @@ -151,19 +174,11 @@ Module MutexSets. set_solver. Qed. - Lemma alloc_mutex_set_map : - ⊢ |==> ∃ γ, mutex_set_map γ ∅. - Proof. - iMod (own_alloc (reserve ∅)) as (γ) "Hmap". - { intros th. rewrite /reserve. case_decide; done. } - iModIntro. iExists γ. iExact "Hmap". - Qed. - (* does not need the update, but is implementation detail. *) - Lemma mutex_sets_alloc_thread' γ T th : + Lemma mutex_sets_alloc_thread' T th : th ∉ T -> - mutex_set_map γ T |-- - mutex_set_map γ (T ∪ {[th]}) ** my_mutexes γ th (CoPset ⊤). + mutex_set_map T |-- + mutex_set_map (T ∪ {[th]}) ** my_mutexes th (CoPset ⊤). Proof. intros Hfresh. rewrite /mutex_set_map /my_mutexes -own_op. @@ -185,20 +200,20 @@ Module MutexSets. by rewrite Heq. Qed. - Lemma mutex_sets_alloc_thread γ T th : + Lemma mutex_sets_alloc_thread T th : th ∉ T -> - mutex_set_map γ T |-- - (|==> mutex_set_map γ (T ∪ {[th]}) ** - my_mutexes γ th (CoPset ⊤)). + mutex_set_map T |-- + (|==> mutex_set_map (T ∪ {[th]}) ** + my_mutexes th (CoPset ⊤)). Proof. iIntros (Hfresh) "Hmap". iModIntro. - iApply (mutex_sets_alloc_thread' γ T th Hfresh with "Hmap"). + iApply (mutex_sets_alloc_thread' T th Hfresh with "Hmap"). Qed. - Lemma mutex_sets_free_thread γ T th : + Lemma mutex_sets_free_thread T th : th ∈ T -> - mutex_set_map γ T ** my_mutexes γ th (CoPset ⊤) |-- - mutex_set_map γ (T \ {[th]}). + mutex_set_map T ** my_mutexes th (CoPset ⊤) |-- + mutex_set_map (T \ {[th]}). Proof. intros Hmember. rewrite /mutex_set_map /my_mutexes -own_op. @@ -220,8 +235,8 @@ Module MutexSets. by rewrite Heq. Qed. - Lemma mutex_set_map_empty_no_my_mutexes γ th (N : namespace) : - mutex_set_map γ ∅ ** my_mutexes γ th (CoPset (↑N)) |-- False. + Lemma mutex_set_map_empty_no_my_mutexes th (N : namespace) : + mutex_set_map ∅ ** my_mutexes th (CoPset (↑N)) |-- False. Proof. rewrite /mutex_set_map /my_mutexes. iIntros "[Hmap Hmy]". @@ -234,10 +249,10 @@ Module MutexSets. set_solver. Qed. - Lemma my_mutexes_alloc_mutex_name γ th (E N : coPset) : + Lemma my_mutexes_alloc_mutex_name th (E N : coPset) : N ⊆ E -> - my_mutexes γ th (CoPset E) |-- - my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N). + my_mutexes th (CoPset E) |-- + my_mutexes th (CoPset (E \ N)) ** my_mutexes th (CoPset N). Proof. intros Hsub. rewrite /my_mutexes -own_op discrete_fun_singleton_op. @@ -247,20 +262,20 @@ Module MutexSets. done. Qed. - Lemma my_mutexes_split γ th (E1 E2 : coPset) : + Lemma my_mutexes_split th (E1 E2 : coPset) : E1 ## E2 -> - my_mutexes γ th (CoPset (E1 ∪ E2)) ⊣⊢ - my_mutexes γ th (CoPset E1) ** my_mutexes γ th (CoPset E2). + my_mutexes th (CoPset (E1 ∪ E2)) ⊣⊢ + my_mutexes th (CoPset E1) ** my_mutexes th (CoPset E2). Proof. intros Hdisjoint. rewrite /my_mutexes -own_op discrete_fun_singleton_op. by rewrite coPset_disj_union. Qed. - Lemma my_mutexes_join_mutex_name γ th (E N : coPset) : + Lemma my_mutexes_join_mutex_name th (E N : coPset) : N ⊆ E -> - my_mutexes γ th (CoPset (E \ N)) ** my_mutexes γ th (CoPset N) |-- - my_mutexes γ th (CoPset E). + my_mutexes th (CoPset (E \ N)) ** my_mutexes th (CoPset N) |-- + my_mutexes th (CoPset E). Proof. intros Hsub. rewrite -my_mutexes_split; last set_solver. @@ -272,28 +287,28 @@ Module MutexSets. End theory. (* an example that two threads can allocate the same namespace token. *) - Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !G Σ} + Lemma my_mutexes_alloc_eg : forall `{Σ : cpp_logic, !mutex_set_preG Σ} (th1 th2 : thread_idT) (N : coPset), th1 ≠ th2 -> - ⊢ |==> ∃ γ, - my_mutexes γ th1 (CoPset N) ** - my_mutexes γ th2 (CoPset N) ** - (mutex_set_map γ {[th1; th2]} ** - my_mutexes γ th1 (CoPset (⊤ \ N)) ** - my_mutexes γ th2 (CoPset (⊤ \ N))). + ⊢ |==> ∃ _ : mutex_set_G Σ, + my_mutexes th1 (CoPset N) ** + my_mutexes th2 (CoPset N) ** + (mutex_set_map {[th1; th2]} ** + my_mutexes th1 (CoPset (⊤ \ N)) ** + my_mutexes th2 (CoPset (⊤ \ N))). Proof. intros until N. intros Hneq. - iMod alloc_mutex_set_map as (γ) "Hmap". - iMod (mutex_sets_alloc_thread γ ∅ th1 ltac:(set_solver) + iMod alloc_mutex_set_map as (HG) "Hmap". + iMod (mutex_sets_alloc_thread ∅ th1 ltac:(set_solver) with "Hmap") as "[Hmap Ht1]". iEval (rewrite left_id_L) in "Hmap". - iMod (mutex_sets_alloc_thread γ {[th1]} th2 ltac:(set_solver) + iMod (mutex_sets_alloc_thread {[th1]} th2 ltac:(set_solver) with "Hmap") as "[Hmap Ht2]". - iDestruct (my_mutexes_alloc_mutex_name γ th1 ⊤ N ltac:(set_solver) + iDestruct (my_mutexes_alloc_mutex_name th1 ⊤ N ltac:(set_solver) with "Ht1") as "[Hr1 Hn1]". - iDestruct (my_mutexes_alloc_mutex_name γ th2 ⊤ N ltac:(set_solver) + iDestruct (my_mutexes_alloc_mutex_name th2 ⊤ N ltac:(set_solver) with "Ht2") as "[Hr2 Hn2]". - iModIntro. iExists γ. iFrame. + iModIntro. iExists HG. iFrame. Qed. #[global] Hint Opaque mutex_set_map my_mutexes : sl_opacity typeclass_instances. diff --git a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v index fbe41b18..a5099c77 100644 --- a/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v +++ b/rocq-brick-libstdcpp/proof/mutex/demo_cpp_proof.v @@ -34,7 +34,8 @@ Definition P `{Σ : cpp_logic, σ : genv} (this : ptr) : TT -t> mpred := sl.lock Definition CR - `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, + !std_recursive_mutex.G Σ, !HasStdThreads Σ} (γ : std_recursive_mutex.gname) (q : cQp.t) : Rep := structR "C" q ** as_Rep (fun this : ptr => @@ -48,6 +49,7 @@ Section std_recursive_mutex. Import std_recursive_mutex. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. Lemma acquireable_update_equiv {TT : tele} this γ qt th f t1 t2 P : @@ -65,6 +67,7 @@ End std_recursive_mutex. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. #[global] Instance: LearnEq2 CR'. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v index 5018814c..6f71d058 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock.v @@ -11,6 +11,7 @@ NES.Begin unique_lock. Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!mutex.G Σ}. Import R_unfold. diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v index c6e52929..36f540a2 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/unique_lock_recursive_mutex.v @@ -12,6 +12,7 @@ NES.Begin unique_lock. Context `{Σ : cpp_logic} {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. Import R_unfold. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index 3d09c545..d712b0da 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -13,6 +13,7 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Export lock_ghost2.mutex_set_G (mutex_set_G). Import linearity. @@ -24,27 +25,21 @@ Module Type MUTEX_PREDS. Parameter cpp_ty : type. Parameter mutex_inv_namespace : gname -> namespace. - (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) - Parameter pool_name : gname -> iprop.gname. - Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. #[global] Arguments G {_ _} Σ : assert. - (** Registration uses the same mutex-set model as the state predicates. *) - #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. - (** [cQp.t] describes the permissions transferred by lock and unlock. The permission might involve points-to, so use cQp.t instead of QP. *) Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. - Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := - token g qt ** MutexSets.my_mutexes (pool_name g) th + token g qt ** MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace g). #[global] Hint Opaque not_locked : sl_opacity typeclass_instances. - Parameter locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, + Parameter locked : forall `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv}, ptr -> gname -> thread_idT -> cQp.t -> mpred. #[global] Declare Instance token_fractional @@ -52,38 +47,40 @@ Module Type MUTEX_PREDS. #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). #[global] Instance not_locked_timeless - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : Timeless (not_locked this γ th q). Proof. rewrite /not_locked. apply _. Qed. (** Each thread has at most one handle to attempt locking this mutex. *) #[global] Instance not_locked_exclusive - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th : Exclusive1 (not_locked this γ th). Proof. intros q1 q2. rewrite /not_locked. iIntros "[_ H1] [_ H2]". have Hoverlap : ~ ((↑mutex_inv_namespace γ : coPset) ## ↑mutex_inv_namespace γ). { have Hnonempty := nclose_non_empty (mutex_inv_namespace γ). set_solver. } - iDestruct (MutexSets.my_mutexes_exclusive _ th + iDestruct (MutexSets.my_mutexes_exclusive th (↑mutex_inv_namespace γ) (↑mutex_inv_namespace γ) Hoverlap with "[$H1 $H2]") as %[]. Qed. #[global] Declare Instance locked_timeless - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). #[global] Declare Instance locked_exclusive - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). #[global] Declare Instance locked_WeaklyObjective - `{Σ : cpp_logic, !G Σ} {σ : genv} this γ th q : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : WeaklyObjective (locked this γ th q). Section with_cpp. Context `{Σ : cpp_logic}. (** Fractional ownership of a mutex guarding the predicate <

>. *) - Parameter R : forall `{!G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. + Parameter R : forall `{MUTEX_SETS : !mutex_set_G Σ, !G Σ} + {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!G Σ}. #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(cfractional,cfracvalid,ascfractional)] derive R. @@ -134,18 +131,18 @@ Module Type MUTEX_PREDS. Initializing [this |-> R old 1$m emp] probably depends on the mutex implementation. *) Axiom init_R : forall (this : ptr) (old : gname) - (pool : iprop.gname) (N : namespace) (P : mpred), + (N : namespace) (P : mpred), WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- (|={⊤}=> ∃ g, - [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** + [| mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). (** FIXME do we really need this? *) Axiom locked_my_mutexes_exclusive : forall - `{Σ : cpp_logic, !G Σ} {σ : genv} this g th qt, + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g th qt, locked this g th qt ** - MutexSets.my_mutexes (pool_name g) th + MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. End with_cpp. @@ -158,6 +155,7 @@ Module mutex_spec (Preds : MUTEX_PREDS). Section with_cpp. Import Preds. Context `{Σ : cpp_logic} {σ : genv}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. @@ -277,7 +275,9 @@ Module StdMutex (Preds : MUTEX_PREDS with Definition cpp_ty := "std::mutex"%cpp_ Module Spec := mutex_spec Preds. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. (* FIXME can we delete this? *) Section with_RepFor. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 08fc9a94..78edcc42 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -11,6 +11,7 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Export lock_ghost2.mutex_set_G (mutex_set_G). Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Import linearity. @@ -99,20 +100,17 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). Existing Class G. #[global] Arguments G {_ _} Σ : assert. - (* FIXME (pool_name g) should be a singleton gname *) - (** Ghost name of the ghost state that keeps track of a pool of gnames for each thread. *) - Parameter pool_name : gname -> iprop.gname. Parameter rmutex_inv_namespace : gname -> namespace. - #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. + Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. #[global] Declare Instance token_fractional `{Σ : cpp_logic, !G Σ} g : CFractional (token g). #[global] Declare Instance token_timeless `{Σ : cpp_logic, !G Σ} g qt : Timeless (token g qt). - Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := - token g qt ** MutexSets.my_mutexes (pool_name g) th + token g qt ** MutexSets.my_mutexes th (CoPset $ ↑rmutex_inv_namespace g). Parameter locked : forall `{Σ : cpp_logic, !G Σ, σ : genv}, ptr -> gname -> cQp.t -> thread_idT -> nat -> mpred. @@ -120,13 +118,15 @@ Module Type RECURSIVE_MUTEX_PREDS (T : MutexCPPName). (* abstract predicate of owership of a recursive mutex that protects some resource [P], e.g. in the form of [inv _ P]. Note that it only represents the mutex data structure, not the ownership of [P]. *) - Parameter R : forall `{Σ : cpp_logic, !G Σ} + Parameter R : forall `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> mpred -> Rep. #[global] Hint Opaque token not_locked locked R : sl_opacity typeclass_instances. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. #[global] Instance not_locked_timeless {σ : genv} this g qt th : Timeless (not_locked this g qt th). @@ -151,7 +151,7 @@ Module recursive_mutex_spec (T : MutexCPPName) (** Ownership of a thread's acquisition state. Operations separately require [current_thread th] to establish that the caller is that thread. *) sl.lock - Definition acquireable `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition acquireable `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) {TT : tele} (s : acquire_state TT) (P : TT -t> mpred) : mpred := @@ -162,7 +162,9 @@ Module recursive_mutex_spec (T : MutexCPPName) #[global] Hint Opaque acquireable : sl_opacity typeclass_instances. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. Definition lock_spec : ptr -> WpSpec mpred val val := @@ -253,10 +255,10 @@ Module recursive_mutex_spec (T : MutexCPPName) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{pool N TT P xs} |> @tele_app TT (fun _ => mpred) P xs + \pre{N TT P xs} |> @tele_app TT (fun _ => mpred) P xs \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) \post Exists g, - [| pool_name g = pool /\ rmutex_inv_namespace g = N |] ** + [| rmutex_inv_namespace g = N |] ** this |-> R g 1$m (∃ xs, @tele_app TT (fun _ => mpred) P xs) ** token g 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := @@ -398,7 +400,9 @@ Module StdRecursiveMutex Include Spec. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. Context `{MOD : source ⊧ σ}. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index 73c87cb3..de7011ff 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -36,7 +36,6 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). (** [locked γ qt th n] records [n] acquisitions by [th], using token share [qt]. *) Class lockedG `{Σ : cpp_logic} := { - #[global] sets_G :: MutexSets.G Σ; #[global] tokens_G :: MutexTokens.G Σ; #[local] has_phys_state :: HasOwn (iPropI _Σ) phys_stateUR; @@ -48,7 +47,6 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). Record gname : Set := MkGname { owned_count_id : iprop.gname; - pool_name : iprop.gname; mutex_inv_namespace : namespace; inv_gname : iprop.gname; token_gname : iprop.gname; @@ -93,10 +91,10 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). and registration token. The first lock transfers that permission into the physical invariant; the final unlock returns it. *) sl.lock - Definition locked `{Σ : cpp_logic, !lockedG Σ} + Definition locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !lockedG Σ} (γ : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := match n with - | 0 => MutexSets.my_mutexes γ.(pool_name) th + | 0 => MutexSets.my_mutexes th (CoPset $ ↑γ.(mutex_inv_namespace)) ** token γ qt | S n => owned_count_id_frag γ (Some (th, n)) ** given_token γ qt end. @@ -104,6 +102,7 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). Section locked_with_cpp. Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!lockedG Σ}. Context `{!HasStdThreads Σ}. @@ -208,7 +207,7 @@ cinv ( nroot .@@ "std" .@@ "recursive_mutex" .@ "raw_inv". sl.lock - Definition R `{Σ : cpp_logic, σ : genv, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := + Definition R `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := type_ptrR T.cpp_ty ** as_Rep (fun this => cinv rmutex_N γ.(inv_gname) (∃ owner count, this |-> rawR owner count ** @@ -217,14 +216,14 @@ cinv ( match owner with | None => MutexTokens.token_full γ.(token_gname) | Some th => MutexTokens.token_not_full γ.(token_gname) ** - MutexSets.my_mutexes γ.(pool_name) th + MutexSets.my_mutexes th (CoPset $ ↑γ.(mutex_inv_namespace)) end)) ** pureR (cinv_own γ.(inv_gname) q). (* TODO: add sequential ownership of the physical lock and [|owner = None <-> count = O|]. *) #[only(cfractional,cfracvalid,ascfractional)] derive R. - #[global] Instance R_type_ptr `{Σ : cpp_logic, σ : genv, !lockedG Σ} γ q : + #[global] Instance R_type_ptr `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !lockedG Σ} γ q : Observe (type_ptrR T.cpp_ty) (R γ q). Proof. rewrite R.unlock. apply _. Qed. @@ -232,6 +231,7 @@ cinv ( Section base_construction. Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!lockedG Σ}. #[global] Instance R_learn : Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) R). @@ -239,9 +239,9 @@ cinv ( Definition ctor_spec_body : ptr -> WpSpec mpred val val := (\this this - \pre{pool N} emp + \pre{N} emp \post Exists g, - [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** + [| mutex_inv_namespace g = N |] ** this |-> R g 1$m ** token g 1$m). Definition dtor_spec_body : ptr -> WpSpec mpred val val := @@ -308,7 +308,6 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. ; cinv_gname : iprop.gname }. Definition gname := rmutex_gname. - Definition pool_name (g : gname) := at_specs.pool_name g.(lock_gname). Definition rmutex_inv_namespace (g : gname) := at_specs.mutex_inv_namespace g.(lock_gname). @@ -328,13 +327,11 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. #[global] Instance recursive_mutex_G `{Σ : cpp_logic} (H : G Σ) : @recursive_mutexG _ _ Σ := H. - #[global] Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ := _. - Definition rmutex_namespace := nroot .@@ "std" .@@ "recursive_mutex" .@@ "derived". sl.lock - Definition inv_rmutex `{Σ : cpp_logic, !G Σ} + Definition inv_rmutex `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (g : gname) (P : mpred) : mpred := cinv rmutex_namespace g.(cinv_gname) (Exists n th qt, own g.(level_gname) (●E (n, th, qt)) ** @@ -346,13 +343,13 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. (** Fractional ownership of the physical mutex, its invariant lifetime, and the protected resource protocol. *) - Definition R `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} + Definition R `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} (g : gname) (q : cQp.t) (P : mpred) : Rep := at_specs.R g.(lock_gname) q ** pureR (cinv_own g.(cinv_gname) q) ** pureR (inv_rmutex g P). #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(cfractional,cfracvalid,ascfractional)] derive R. - #[global] Instance R_type_ptr `{Σ : cpp_logic, !G Σ} + #[global] Instance R_type_ptr `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} g q P : Observe (type_ptrR cpp_ty) (R g q P). Proof. rewrite /R. apply _. Qed. @@ -374,14 +371,14 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Timeless (given_token g qt th n). Proof. rewrite /given_token. apply _. Qed. - Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := token g qt ** - MutexSets.my_mutexes (pool_name g) th (CoPset $ ↑rmutex_inv_namespace g). + MutexSets.my_mutexes th (CoPset $ ↑rmutex_inv_namespace g). Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := given_token g qt th n. - #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th : + #[global] Instance not_locked_timeless `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g qt th : Timeless (not_locked this g qt th). Proof. rewrite /not_locked. apply _. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th n : @@ -390,7 +387,9 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. #[global] Hint Opaque not_locked locked : sl_opacity typeclass_instances. Section rules. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context `{!HasStdThreads Σ} {σ : genv}. @@ -430,6 +429,7 @@ Module RecursiveMutexRefinement (T : MutexCPPName). Section with_cpp. Context `{Σ : cpp_logic} `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!G Σ}. Context `{HOV : !HasOwnValid mpredI cmraR, HOU : !HasOwnUpd mpredI cmraR}. @@ -441,7 +441,7 @@ Section with_cpp. Proof using MOD HOV HOU. work. iModIntro; work. - iExists pool, N. work. + iExists N. work. (* The owner slot is unused while the initial count is zero. *) set th : thread_idT := inhabitant. iMod (own_alloc (●E (O, th, (1$m)%cQp) ⋅ ◯E (O, th, (1$m)%cQp))) as (g) "(? & ?)". @@ -450,7 +450,7 @@ Section with_cpp. wname [at_specs.token] "Htoken". iMod (cinv_alloc with "[-a Htoken]") as (ginv) "(#Hinv & Hown)"; last first. - iExists {| lock_gname := t; level_gname := g; cinv_gname:= ginv |}. - rewrite /R /token /pool_name /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock /=. + rewrite /R /token /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock /=. iModIntro. go with br_erefl $usenamed=true. - ework with br_erefl. @@ -463,7 +463,7 @@ Section with_cpp. (Spec.dtor_spec this). Proof using MOD HOV HOU. work. - rewrite /R /token /pool_name /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock. + rewrite /R /token /rmutex_inv_namespace !_at_sep !_at_pureR inv_rmutex.unlock. work. iMod (cinv_cancel with "[$] [$]") as (n th qt) "(>? & ?)"; [done..|]. @@ -493,7 +493,7 @@ Section with_cpp. (∃ t, [| acquire n t |] ∗ ▷ acquireable this g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token - /token /pool_name /rmutex_inv_namespace. + /token /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". destruct n as [|n args]; simpl; [iExists 0 | iExists (S n)]. @@ -535,7 +535,7 @@ Section with_cpp. acquireable this g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token - /token /pool_name /rmutex_inv_namespace. + /token /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". iDestruct (own_valid_2 with "Hn [$]") as %[=]%excl_auth_agree_L; subst. @@ -546,9 +546,9 @@ Section with_cpp. iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; first apply (excl_auth_update _ _ (n, th, qt)). rewrite /not_locked /locked /given_token /token - /pool_name /rmutex_inv_namespace release.unlock; destruct n. + /rmutex_inv_namespace release.unlock; destruct n. 1: rewrite [in at_specs.locked _ _ _ 0]at_specs.locked.unlock /=. - 1: iDestruct select (MutexSets.my_mutexes _ _ _ ** at_specs.token _ _) as "[? ?]". + 1: iDestruct select (MutexSets.my_mutexes _ _ ** at_specs.token _ _) as "[? ?]". all: iFrame "#∗". all: iMod ("Hclose" with "[-]") as "_"; ework $usenamed=true with br_erefl; done. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index d20ff142..1cc666c7 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -3,6 +3,7 @@ Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Export lock_ghost2.mutex_set_G (mutex_set_G). Module scoped_lock. Section with_cpp. @@ -17,6 +18,7 @@ Module scoped_lock. Section with_threads. Context {σ : genv}. Context `{HAS_THREADS : !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!mutex.G Σ}. #[global] Instance: LearnEqF1 R := ltac:(solve_learnable). diff --git a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v index a7ef647a..cc69f0c8 100644 --- a/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v +++ b/rocq-brick-libstdcpp/proof/shared_mutex/shared_mutex.v @@ -10,6 +10,7 @@ Require Export skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.shared_mutex.inc_hpp. Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Export lock_ghost2.mutex_set_G (mutex_set_G). Import linearity. @@ -21,15 +22,13 @@ Module Type SHARED_MUTEX_PREDS. Existing Class G. #[global] Arguments G {_ _} Σ : assert. - Parameter pool_name : gname -> iprop.gname. Parameter smutex_inv_namespace : gname -> namespace. - #[global] Declare Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ. Parameter token : forall `{Σ : cpp_logic, !G Σ}, gname -> cQp.t -> mpred. - Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := - token g qt ** MutexSets.my_mutexes (pool_name g) th + token g qt ** MutexSets.my_mutexes th (CoPset $ ↑smutex_inv_namespace g). Parameter locked : forall `{Σ : cpp_logic, !G Σ} {σ : genv}, @@ -40,14 +39,16 @@ Module Type SHARED_MUTEX_PREDS. ptr -> gname -> thread_idT -> cQp.t -> Qp -> mpred. (** A shared mutex guarding a fractional predicate [P]. *) - Parameter R : forall `{Σ : cpp_logic, !G Σ} + Parameter R : forall `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv}, gname -> cQp.t -> (Qp -> mpred) -> Rep. #[global] Hint Opaque token not_locked locked reader_locked R : sl_opacity typeclass_instances. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. #[global] Declare Instance token_fractional g : CFractional (token g). #[global] Declare Instance token_timeless g qt : Timeless (token g qt). @@ -64,7 +65,7 @@ Module Type SHARED_MUTEX_PREDS. iIntros "[_ H1] [_ H2]". have Hoverlap : ~ ((↑smutex_inv_namespace g : coPset) ## ↑smutex_inv_namespace g). { have Hnonempty := nclose_non_empty (smutex_inv_namespace g). set_solver. } - iDestruct (MutexSets.my_mutexes_exclusive _ th + iDestruct (MutexSets.my_mutexes_exclusive th (↑smutex_inv_namespace g) (↑smutex_inv_namespace g) Hoverlap with "[$H1 $H2]") as %[]. Qed. @@ -95,17 +96,19 @@ End SHARED_MUTEX_PREDS. Module shared_mutex_spec (Preds : SHARED_MUTEX_PREDS). Import Preds. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ} {σ : genv}. (* ctor and dtor are the same as mutex.v, except that [P] is fractional. *) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this - \pre{pool N P} ▷P 1%Qp + \pre{N P} ▷P 1%Qp \require Fractional P \require ∀ q, Objective (P q) \post Exists g, - [| pool_name g = pool /\ smutex_inv_namespace g = N |] ** + [| smutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := @@ -272,7 +275,9 @@ Module StdSharedMutex Module Spec := shared_mutex_spec Preds. Section with_cpp. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context {HAS_THREADS : HasStdThreads Σ}. Context `{MOD : source ⊧ σ}. diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index 3324bb90..0fe1e071 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -2,6 +2,7 @@ Require Import skylabs.bi.tls_modalities. Require Import skylabs.auto.cpp.prelude.proof. Require Import skylabs.brick.libstdcpp.runtime.pred. Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. +Export lock_ghost2.mutex_set_G (mutex_set_G). Require Import skylabs.brick.libstdcpp.thread.thread_hpp. Import linearity. @@ -10,7 +11,7 @@ Import linearity. Section with_cpp. Context `{Σ : cpp_logic} {σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!MutexSets.G Σ}. + Context `{!mutex_set_G Σ}. (** Exclusive ownership of a thread object. [R $ None] is already joined thread and non-joinable; @@ -24,17 +25,18 @@ Section with_cpp. (** The mutex-set map tracks spawned thread IDs and supplies each child with its [MutexSets.my_mutexes] handle. *) - Definition spawned_threads_inv (N : namespace) (γ : iprop.gname) : mpred := - inv N (Exists tids, MutexSets.mutex_set_map γ tids). + Definition spawned_threads_inv (N : namespace) : mpred := + inv N (Exists tids, MutexSets.mutex_set_map tids). + (** Install the map allocated with the ambient [mutex_set_G] instance. *) Lemma spawned_threads_inv_alloc N : - ⊢ |={⊤}=> ∃ γ, spawned_threads_inv N γ. + MutexSets.mutex_set_map ∅ |-- (|={⊤}=> spawned_threads_inv N). Proof. - iMod MutexSets.alloc_mutex_set_map as (γ) "Hmap". - iMod (inv_alloc N _ (Exists tids, MutexSets.mutex_set_map γ tids) + iIntros "Hmap". + iMod (inv_alloc N _ (Exists tids, MutexSets.mutex_set_map tids) with "[Hmap]") as "Hinv". { iNext. iExists ∅. done. } - iModIntro. iExists γ. done. + iModIntro. done. Qed. Definition entry_type : type := Tfunction (FunctionType Tvoid []). @@ -53,12 +55,12 @@ Section with_cpp. Definition spawn_spec_body : ptr -> WpSpec mpred val val := (\this this \arg{f : ptr} "f" (Vptr f) - \pre{N γ} spawned_threads_inv N γ + \pre{N} spawned_threads_inv N \persist{parent} current_thread parent \pre{Q : mpred} [| WeaklyObjective Q |] \pre (∀ child, - (MutexSets.my_mutexes γ child (coPset.CoPset ⊤) -* + (MutexSets.my_mutexes child (coPset.CoPset ⊤) -* (* FIXME is the usage of this wp_fptr correct? *) wp_fptr (σ.(genv_tu).(types)) entry_type f [] (* f returns void so Q does not depend on return value *) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 452e765a..bfd97616 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -22,7 +22,6 @@ Module CustomMutexPreds <: MUTEX_PREDS. Import MutexSets MutexTokens OwnerTid. Record state_gname : Set := MkStateGname { - pool_gname : iprop.gname; token_gname : iprop.gname; owner_gname : iprop.gname; state_namespace : namespace; @@ -35,8 +34,6 @@ Module CustomMutexPreds <: MUTEX_PREDS. cinv_gname : iprop.gname; }. Definition gname : Set := mutex_gname. - Definition pool_name (γ : gname) : iprop.gname := - γ.(lock_state_gname).(pool_gname). Definition mutex_inv_namespace (g : gname) : namespace := g.(state_namespace). @@ -45,7 +42,6 @@ Module CustomMutexPreds <: MUTEX_PREDS. g.(state_namespace) .@ "held". Class stateG `{Σ : cpp_logic} := { - #[global] sets_G :: MutexSets.G Σ; #[global] tokens_G :: MutexTokens.G Σ; #[global] owners_G :: OwnerTid.G Σ; }. @@ -60,16 +56,16 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := state_token γ q. - Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := token γ q ** - MutexSets.my_mutexes (pool_name γ) th (coPset.CoPset $ ↑mutex_inv_namespace γ). + MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace γ). - Definition owner_token `{Σ : cpp_logic, !G Σ} + Definition owner_token `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := MutexTokens.given_token γ.(token_gname) q ** OwnerTid.owner_tid_frag γ.(owner_gname) (Some th) ** - MutexSets.my_mutexes γ.(pool_gname) th (coPset.CoPset $ ↑held_namespace γ). + MutexSets.my_mutexes th (coPset.CoPset $ ↑held_namespace γ). #[global] Instance state_token_fractional `{Σ : cpp_logic, !G Σ} γ : CFractional (state_token γ). @@ -83,11 +79,11 @@ Module CustomMutexPreds <: MUTEX_PREDS. Proof. rewrite /state_token. apply _. Qed. #[global] Instance owner_token_timeless - `{Σ : cpp_logic, !G Σ} γ th q : Timeless (owner_token γ th q). + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ th q : Timeless (owner_token γ th q). Proof. rewrite /owner_token. apply _. Qed. #[global] Instance owner_token_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive2 (owner_token γ). + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ : Exclusive2 (owner_token γ). Proof. intros th1 th2 q1 q2. rewrite /owner_token. iIntros "(_ & F1 & _) (_ & F2 & _)". @@ -98,11 +94,11 @@ Module CustomMutexPreds <: MUTEX_PREDS. and [owner_token]. The held part makes a second registration by the same thread contradictory without opening this invariant. Unlock rejoins the parts. While free, both owner fragments are in the invariant. *) - Definition state `{Σ : cpp_logic, !G Σ} + Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (γ : state_gname) (b : bool) : mpred := (if b then ∃ th, OwnerTid.owner_tid_auth γ.(owner_gname) (Some th) ** - MutexSets.my_mutexes γ.(pool_gname) th + MutexSets.my_mutexes th (coPset.CoPset $ ↑γ.(state_namespace) ∖ ↑held_namespace γ) ** MutexTokens.token_not_full γ.(token_gname) else @@ -111,20 +107,22 @@ Module CustomMutexPreds <: MUTEX_PREDS. MutexTokens.token_full γ.(token_gname))%I. #[global] Instance state_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ b : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ b : WeaklyObjective (state γ b). Proof. rewrite /state. destruct b; apply _. Qed. Section state_laws. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. - Lemma alloc_state (γpool : iprop.gname) (N : namespace) : - ⊢ |==> ∃ γ, [| γ.(pool_gname) = γpool /\ γ.(state_namespace) = N |] ** + Lemma alloc_state (N : namespace) : + ⊢ |==> ∃ γ, [| γ.(state_namespace) = N |] ** state_token γ 1$m ** state γ false. Proof. iMod MutexTokens.alloc as (gt) "[T GT]". iMod (OwnerTid.alloc None) as (go) "[OA OF]". - iModIntro. iExists (MkStateGname γpool gt go N). + iModIntro. iExists (MkStateGname gt go N). iSplit; first done. rewrite /state_token /state /=. iFrame "T". iExists None. iFrame "OA OF". @@ -135,13 +133,13 @@ Module CustomMutexPreds <: MUTEX_PREDS. state γ false ** not_locked this γ th q |-- (|==> state γ true ** owner_token γ th q). Proof. - rewrite /state /not_locked /token /state_token /pool_name /mutex_inv_namespace /owner_token. + rewrite /state /not_locked /token /state_token /mutex_inv_namespace /owner_token. iIntros "[State [T Sets]]". iDestruct "State" as (owner) "(OA & OF & Balance)". iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) with "[$OA $OF]") as "[OA OF]". - iDestruct (MutexSets.my_mutexes_alloc_mutex_name γ.(pool_gname) th + iDestruct (MutexSets.my_mutexes_alloc_mutex_name th (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) with "Sets") as "[Rest Held]". iModIntro. iFrame "GT OF Held". iExists th. iFrame. @@ -151,13 +149,13 @@ Module CustomMutexPreds <: MUTEX_PREDS. state γ true ** owner_token γ th q |-- (|==> state γ false ** not_locked this γ th q). Proof. - rewrite /state /owner_token /not_locked /token /state_token /pool_name /mutex_inv_namespace. + rewrite /state /owner_token /not_locked /token /state_token /mutex_inv_namespace. iIntros "[State (GT & OF & Held)]". iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. injection Heq as ->. iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". - iDestruct (MutexSets.my_mutexes_join_mutex_name γ.(pool_gname) th + iDestruct (MutexSets.my_mutexes_join_mutex_name th (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) with "[$Sets $Held]") as "Sets". iModIntro. iFrame "Sets T". iExists (Some th). iFrame. @@ -189,7 +187,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. #[global] Arguments globals /. - Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m th ** owner_token γ th q. @@ -203,24 +201,24 @@ Module CustomMutexPreds <: MUTEX_PREDS. #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). Proof. rewrite /token. apply _. Qed. - #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance not_locked_timeless `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : Timeless (not_locked this γ th q). Proof. rewrite /not_locked. apply _. Qed. - #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance not_locked_exclusive `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th : Exclusive1 (not_locked this γ th). Proof. intros q1 q2. rewrite /not_locked. iIntros "[_ F1] [_ F2]". have Hoverlap : ~ ((↑ mutex_inv_namespace γ : coPset) ## ↑ mutex_inv_namespace γ). { have Hnonempty := nclose_non_empty (mutex_inv_namespace γ). set_solver. } - iDestruct (MutexSets.my_mutexes_exclusive _ th + iDestruct (MutexSets.my_mutexes_exclusive th (↑ mutex_inv_namespace γ) (↑ mutex_inv_namespace γ) Hoverlap with "[$F1 $F2]") as %[]. Qed. - #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance locked_timeless `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : Timeless (locked this γ th q). Proof. rewrite /locked. apply _. Qed. - #[global] Instance locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance locked_exclusive `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ : Exclusive2 (locked this γ). Proof. intros th1 th2 q1 q2. rewrite /locked. apply observe_2_sep_r. apply _. Qed. @@ -229,17 +227,17 @@ Module CustomMutexPreds <: MUTEX_PREDS. WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. - #[global] Instance locked_WeaklyObjective `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance locked_WeaklyObjective `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : WeaklyObjective (locked this γ th q). Proof. rewrite /locked /owner_token /MutexTokens.given_token. apply _. Qed. - Lemma locked_my_mutexes_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + Lemma locked_my_mutexes_exclusive `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g th qt : locked this g th qt ** - MutexSets.my_mutexes (pool_name g) th + MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. Proof. - rewrite /locked /owner_token /pool_name /mutex_inv_namespace. + rewrite /locked /owner_token /mutex_inv_namespace. iIntros "[(_ & _ & _ & Held) Names]". iApply (MutexSets.my_mutexes_exclusive with "[$Held $Names]"). have Hnonempty := nclose_non_empty (held_namespace g). @@ -249,7 +247,9 @@ Module CustomMutexPreds <: MUTEX_PREDS. Qed. Section with_Σ. - Context `{Σ : cpp_logic, !G Σ}. + Context `{Σ : cpp_logic}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. (** The physical lock bit agrees with the abstract ghost state. The protected resources and cleared owner field are available while free. *) @@ -310,20 +310,20 @@ Module CustomMutexPreds <: MUTEX_PREDS. { do_try_lock := do_try_lock }. Lemma init_R (this : ptr) (old : gname) - (pool : iprop.gname) (N : namespace) (P : mpred) : + (N : namespace) (P : mpred) : WeaklyObjective P -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- - (|={⊤}=> ∃ g, [| pool_name g = pool /\ mutex_inv_namespace g = N |] ** + (|={⊤}=> ∃ g, [| mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). Proof. intros HP. iIntros "(HR & T & P)". iEval (rewrite /R _at_sep _at_as_Rep) in "HR". iDestruct "HR" as "(S & #CI & CO)". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. - iMod (alloc_state pool N) as (gs) "(%Hpool & Tnew & Stnew)". + iMod (alloc_state N) as (gs) "(%Hnamespace & Tnew & Stnew)". iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. - iModIntro. iExists (MkGname gs gi). - iSplit; first (iPureIntro; exact Hpool). + iSplit; first (iPureIntro; exact Hnamespace). rewrite /R _at_sep _at_as_Rep /token /mutex_inv /=. iFrame "CInew S COnew Tnew". - iNext. rewrite /mutex_inv. @@ -355,8 +355,9 @@ Module custom_mutex. Abbreviation IR := CustomMutexPreds.R. Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, - !CustomMutexPreds.G Σ}. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!CustomMutexPreds.G Σ}. Context `{MOD : source ⊧ σ}. @@ -592,7 +593,7 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_id_specs.thread_idR _ _] "Owner". - iMod (alloc_state 1%positive (nroot .@@ "MyMutex" .@ "inv_namespace")) as (gs) "(_ & T & State)". + iMod (alloc_state (nroot .@@ "MyMutex" .@ "inv_namespace")) as (gs) "(_ & T & State)". iMod (cinv_alloc with "[L P Owner State]") as (gi) "[#CI CO]"; last first. - iModIntro. iExists (MkGname gs gi). diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index 08be183f..f1461a9c 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -13,7 +13,6 @@ Require Import skylabs.brick.libstdcpp.cassert.spec. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. Require Import skylabs.brick.libstdcpp.thread.spec. -Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. NES.Open std.atomic. @@ -40,8 +39,6 @@ Module CustomRecursiveMutexPreds cinv_gname : iprop.gname; }. Definition gname : Set := mutex_gname. - Definition pool_name (g : gname) : iprop.gname := - mutex.pool_name g.(inner_gname). Definition rmutex_inv_namespace (g : gname) : namespace := mutex.mutex_inv_namespace g.(inner_gname). @@ -61,7 +58,6 @@ Module CustomRecursiveMutexPreds #[global] Arguments G {_ _} Σ : assert. #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. - #[global] Instance sets_G `{Σ : cpp_logic, !G Σ} : MutexSets.G Σ := _. Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := @@ -157,7 +153,7 @@ Module CustomRecursiveMutexPreds | Some (th, _) => th end. - Definition I `{Σ : cpp_logic, !G Σ, σ : genv} + Definition I `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} (this : ptr) (g : gname) : mpred := ∃ owner, this ,, _field "MyRecursiveMutex::m_owner" |-> @@ -178,7 +174,7 @@ Module CustomRecursiveMutexPreds | None => emp end. - Definition R `{Σ : cpp_logic, !G Σ} + Definition R `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} (g : gname) (q : cQp.t) (P : mpred) : Rep := structR class_name q$m ** @@ -191,11 +187,11 @@ Module CustomRecursiveMutexPreds #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive R. #[global] Instance R_learnable - `{Σ : cpp_logic, !G Σ, σ : genv, !HasStdThreads Σ} : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv, !HasStdThreads Σ} : Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). Proof. solve_learnable. Qed. - Definition not_locked `{Σ : cpp_logic, !G Σ, σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := mutex.not_locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt. @@ -207,7 +203,7 @@ Module CustomRecursiveMutexPreds #[global] Hint Opaque not_locked locked : sl_opacity typeclass_instances. #[global] Instance I_timeless - `{Σ : cpp_logic, !G Σ, σ : genv} this g : Timeless (I this g). + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g : Timeless (I this g). Proof. rewrite /I. apply bi.exist_timeless. intros [[th qt]|]; apply _. @@ -221,7 +217,7 @@ Module CustomRecursiveMutexPreds Timeless (locked this g qt th n). Proof. rewrite /locked /countR. apply _. Qed. #[global] Instance not_locked_timeless - `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g qt th : Timeless (not_locked this g qt th). Proof. rewrite /not_locked. apply _. Qed. @@ -234,7 +230,7 @@ Module CustomRecursiveMutexPreds `{Σ : cpp_logic, !G Σ} g owner : WeaklyObjective (owner_frag g owner). Proof. rewrite /owner_frag. apply _. Qed. #[global] Instance I_WeaklyObjective - `{Σ : cpp_logic, !G Σ, σ : genv} this g : + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g : WeaklyObjective (I this g). Proof. rewrite /I. apply exists_weakly_objective. @@ -255,7 +251,9 @@ Module custom_recursive_mutex. Import Spec (acquireable). #[local] Hint Opaque acquireable I protected countR : sl_opacity typeclass_instances. Section with_cpp. - Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ, !G Σ}. + Context `{Σ : cpp_logic, σ : genv, !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Context `{MOD : source ⊧ σ}. (** The wrapper adds its global sentinel and the count's objectivity @@ -327,15 +325,15 @@ Module custom_recursive_mutex. wname [_ |-> ulonglongR _ _] "Count". iDestruct select (@tele_app TT (fun _ => mpred) P xs) as "P". iMod (mutex.init_R (this ,, _field "MyRecursiveMutex::m_lock") - g pool N (protected this go (∃ xs, @tele_app TT (fun _ => mpred) P xs)) + g N (protected this go (∃ xs, @tele_app TT (fun _ => mpred) P xs)) with "[Inner Token Count Frag P]") as (inner) "(%Hnames & Inner & Token)". { iFrame "Inner Token". iNext. rewrite /protected /countR. iFrame. } wname [_ |-> atomic.R _ _ _] "Owner". iMod (cinv_alloc ⊤ (mutex.mutex_inv_namespace inner) with "[Owner Auth]") as (gi) "[#CI CO]"; last first. - iModIntro. go. iExists (MkGname inner go gi). - iSplit; first (iPureIntro; split; reflexivity). + iSplit; first (iPureIntro; reflexivity). rewrite /token /R _at_sep _at_as_Rep - /I /pool_name /=. + /I /=. iFrame "CI". iFrame. - iNext. rewrite /I /=. iExists None. iFrame. - change (WeaklyObjective (I this (MkGname inner go 1%positive))). diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v index b101b271..bab3d70e 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -13,7 +13,9 @@ Require Import skylabs.brick.libstdcpp.test.mutex.double_incr_ghost. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!mutex.G Σ, !double_incr_protocol.G Σ}. + Context `{!double_incr_protocol.G Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!mutex.G Σ}. cpp.spec "double_incr()" as double_incr_spec_body from source with ( \prepost{g q γ} _global "m" |-> mutex.R g q @@ -88,7 +90,7 @@ Section with_cpp. Lemma worker_entry `{MOD : source ⊧ σ} g γ : double_incr_spec_body ** worker_start g γ |-- ∀ child, - MutexSets.my_mutexes (mutex.pool_name g) child (coPset.CoPset ⊤) -* + MutexSets.my_mutexes child (coPset.CoPset ⊤) -* wp_fptr (σ.(genv_tu).(types)) thread.spec.entry_type (_global "double_incr()") [] (fun _ => double_incr_protocol.done γ (_global "x")). @@ -102,8 +104,8 @@ Section with_cpp. iDestruct (monPred_atleast_exactly_at threadTI child) as "HT". iModIntro. iDestruct "Hstart" as "(Hm & Htoken & Hfrac)". - iDestruct (MutexSets.my_mutexes_alloc_mutex_name (mutex.pool_name g) - child ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + iDestruct (MutexSets.my_mutexes_alloc_mutex_name child ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "Hset") as "[Hrest Hname]". iAssert (mutex.not_locked (_global "m") g child ((1 / 2)$m)%cQp) with "[Htoken Hname]" as "Hnotlocked". @@ -126,6 +128,10 @@ Section with_cpp. Assume the mutex is initialized with an empty invariant (assocaited with γold), which can later be updated to a new invariant with init_R. *) cpp.spec "main()" as main_spec from source with ( + (* FIXME if we have fixed the gname for mutex_set_map by the instance, we + have to assume a [mutex_set_map ∅] in the precondition here because if we + allocate a new one, we can't prove that it has the same gname. *) + \pre MutexSets.mutex_set_map ∅ \pre _global "x" |-> uintR 1$m 0 \pre{γold} _global "m" |-> mutex.R γold 1$m emp ** mutex.token γold 1$m \post[Vint 0] double_incr_protocol.evenR (_global "x")). @@ -143,14 +149,14 @@ Section with_cpp. iMod (double_incr_protocol.alloc with "Hx") as (γ) "(HP & Hf1 & Hf2)". iDestruct select (_global "m" |-> mutex.R γold 1$m emp) as "Hm". iDestruct select (mutex.token γold 1$m) as "Htoken". - iMod (thread.spec.spawned_threads_inv_alloc thread_pool_namespace) - as (pool) "#Hpool". + iDestruct select (MutexSets.mutex_set_map ∅) as "Hmap". + iMod (thread.spec.spawned_threads_inv_alloc thread_pool_namespace + with "Hmap") as "#Hpool". (* Logical invariant setup for the already initialized mutex. *) - iMod (mutex.init_R (_global "m") γold pool (mutex.mutex_inv_namespace γold) + iMod (mutex.init_R (_global "m") γold (mutex.mutex_inv_namespace γold) (double_incr_protocol.protected γ (_global "x")) ltac:(apply _) with "[Hm Htoken HP]") as (g) "(%Hnames & Hm & Htoken)". { iFrame "Hm Htoken". iNext. done. } - destruct Hnames as [Hpool_name _]. subst pool. iDestruct "Hm" as "[Hm1 Hm2]". iEval (rewrite (cfractional_split_half (mutex.token g)) cQp.scale_mut Qp.mul_1_r) in "Htoken". @@ -160,7 +166,7 @@ Section with_cpp. { rewrite /worker_start. iFrame. } go. iDestruct select double_incr_spec_body as "#Hcode". - iExists thread_pool_namespace, (mutex.pool_name g), + iExists thread_pool_namespace, (double_incr_protocol.done γ (_global "x")). iFrame "#". iSplitR; first (iPureIntro; apply _). iSplitL "Hstart1". @@ -169,7 +175,7 @@ Section with_cpp. iDestruct "Hthread1" as (child1) "[% HR1]". iDestruct (observe (type_ptr "std::thread" t1_addr) with "HR1") as "#?". iDestruct "HR1" as "?". go. - iExists thread_pool_namespace, (mutex.pool_name g), + iExists thread_pool_namespace, (double_incr_protocol.done γ (_global "x")). iFrame "#". iSplitR; first (iPureIntro; apply _). iSplitL "Hstart2". diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index fa321438..07b7b41c 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -10,7 +10,7 @@ Import linearity. Module transfer_lock_guard. sl.lock -Definition R `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} +Definition R `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !std_recursive_mutex.G Σ, !HasStdThreads Σ} (mp : ptr) (g : std_recursive_mutex.gname) (q : cQp.t) (P : mpred) : Rep := structR "std::lock_guard" 1$m ** _field "std::lock_guard::_M_device" @@ -22,6 +22,7 @@ Definition R `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThre Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. #[local] Hint Opaque std_recursive_mutex.do_lock std_recursive_mutex.do_unlock : sl_opacity. @@ -69,7 +70,7 @@ Definition account_balanceR `{Σ : cpp_logic, σ : genv} (n : Z) : Rep := (* ownership of [this->mutex] and balanceR proctected by the mutex *) sl.lock Definition account_mutexR - `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !std_recursive_mutex.G Σ, !HasStdThreads Σ} (g : std_recursive_mutex.gname) (q : cQp.t) : Rep := structR "C" q ** as_Rep (fun this : ptr => @@ -83,12 +84,11 @@ Definition account_mutexR [N : namespace], and returns (most of) account_mutexR. *) sl.lock Definition accountR - `{Σ : cpp_logic, σ : genv, !std_recursive_mutex.G Σ, !HasStdThreads Σ} - (gpool : iprop.gname) (N : namespace) (q : cQp.t) : Rep := + `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !std_recursive_mutex.G Σ, !HasStdThreads Σ} + (N : namespace) (q : cQp.t) : Rep := Exists g, account_mutexR g q ** pureR (std_recursive_mutex.token g q) ** - pureR [| std_recursive_mutex.pool_name g = gpool /\ - std_recursive_mutex.rmutex_inv_namespace g = N |]. + pureR [| std_recursive_mutex.rmutex_inv_namespace g = N |]. #[only(type_ptr="C")] derive accountR. (** The two account mutexes use distinct namespaces. *) @@ -111,6 +111,7 @@ Definition account_call (cl : name) : name := Section class_C. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. #[local] Instance balanceR_learn : LearnEq2 account_balanceR. @@ -195,8 +196,8 @@ Section class_C. \arg{f} "f" (Vptr f) \persist type_ptr (Tnamed cl) f \persist{th} current_thread th - \prepost{gpool N q} this |-> accountR gpool N q ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑N)) + \prepost{N q} this |-> accountR N q ** + MutexSets.my_mutexes th (coPset.CoPset (↑N)) \pre{K} ∀ v : Z, this |-> account_balanceR v -* balance_callback cl f (this ,, _field "C::balance") @@ -215,7 +216,7 @@ Section class_C. rewrite /call_spec_body /account_call_internal_body. cbn. iIntros "H". - iDestruct "H" as (f th gpool N q K) + iDestruct "H" as (f th N q K) "(%Hargs & #Hf & #Hth & (HR & Hnames) & Hcallback & Hpost)". iEval (rewrite accountR.unlock _at_exists) in "HR". iDestruct "HR" as (g) "HR". @@ -225,7 +226,7 @@ Section class_C. std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Htoken Hnames]" as "Hacquireable". { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. - destruct Hg as [Hgpool HN]. rewrite Hgpool HN. iFrame "#∗". } + rewrite Hg. iFrame "#∗". } iExists f, g, q, (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) g q th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n) ** K)%I. @@ -260,8 +261,7 @@ Section class_C. iEval (rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked) in "Hacquireable". iDestruct "Hacquireable" as "[Htoken Hnames]". - destruct Hg as [Hgpool HN]. - iEval (rewrite Hgpool HN) in "Hnames". iFrame "Hnames". + iEval (rewrite Hg) in "Hnames". iFrame "Hnames". rewrite accountR.unlock _at_exists. iExists g. rewrite !_at_sep !_at_pureR. iFrame. done. Qed. @@ -283,11 +283,11 @@ Section class_C. \arg{to} "to" (Vptr to) \arg{i} "i" (Vint i) \persist{th} current_thread th - \prepost{gpool qfrom qto} - from |-> accountR gpool from_namespace qfrom ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑from_namespace)) ** - to |-> accountR gpool to_namespace qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑to_namespace)) + \prepost{qfrom qto} + from |-> accountR from_namespace qfrom ** + MutexSets.my_mutexes th (coPset.CoPset (↑from_namespace)) ** + to |-> accountR to_namespace qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑to_namespace)) \post emp ). @@ -299,17 +299,25 @@ Section class_C. | |- context[transfer_lock_guard.R (this ,, _) ?gfrom _ _] => rename gfrom into source_g end. - iDestruct select (MutexSets.my_mutexes _ th + match goal with + | H : std_recursive_mutex.rmutex_inv_namespace source_g = from_namespace |- _ => + rename H into Hfrom_namespace + end. + match goal with + | H : std_recursive_mutex.rmutex_inv_namespace g = to_namespace |- _ => + rename H into Hto_namespace + end. + iDestruct select (MutexSets.my_mutexes th (coPset.CoPset (↑from_namespace))) as "Hfrom_names". - iEval (rewrite -b) in "Hfrom_names". + iEval (rewrite -Hfrom_namespace) in "Hfrom_names". iDestruct select (std_recursive_mutex.token source_g qfrom) as "Hfrom_token". iAssert (std_recursive_mutex.acquireable (this ,, _field "C::mut") (TT := balance_args) source_g qfrom th std_recursive_mutex.NotHeld (fun n : Z => this |-> account_balanceR n))%I with "[Hfrom_names Hfrom_token]" as "?". { rewrite std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. iFrame "#∗". } - iDestruct select (MutexSets.my_mutexes _ th + iDestruct select (MutexSets.my_mutexes th (coPset.CoPset (↑to_namespace))) as "Hto_names". - iEval (rewrite -a -b0) in "Hto_names". + iEval (rewrite -Hto_namespace) in "Hto_names". iDestruct select (std_recursive_mutex.token g qto) as "Hto_token". iAssert (std_recursive_mutex.acquireable (to ,, _field "C::mut") (TT := balance_args) g qto th std_recursive_mutex.NotHeld (fun n : Z => to |-> account_balanceR n))%I @@ -332,7 +340,7 @@ Section class_C. rewrite std_recursive_mutex.release.unlock /=. go. rewrite accountR.unlock std_recursive_mutex.acquireable.unlock /= /std_recursive_mutex.not_locked. go with br_erefl. - rewrite a b b0. go. + rewrite Hfrom_namespace Hto_namespace. go. Qed. (** Link the C implementation and both lock-guard operations. *) @@ -362,14 +370,14 @@ Section class_C. \this this \arg{initial_balance} "initial_balance" (Vint initial_balance) \persist{th} current_thread th - \pre{gpool N} emp - \post this |-> accountR gpool N 1$m + \pre{N} emp + \post this |-> accountR N 1$m ). Lemma ctor_ok : verify[source] ctor_spec. Proof using Type balance_objective. verify_spec; go. - iExists gpool, N, balance_args, (fun n : Z => this |-> account_balanceR n)%I, + iExists N, balance_args, (fun n : Z => this |-> account_balanceR n)%I, (balance_arg initial_balance); go. rewrite -bi.later_intro. go. @@ -380,7 +388,7 @@ Section class_C. cpp.spec "C::~C()" as dtor_spec from source with ( \this this - \pre{gpool N} this |-> accountR gpool N 1$m + \pre{N} this |-> accountR N 1$m \post emp ). @@ -404,10 +412,11 @@ End class_C. Section clients. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. #[local] Instance accountR_learn : - Cbn (Learn (learn_eq ==> learn_eq ==> any ==> learn_hints.fin) accountR). + Cbn (Learn (learn_eq ==> any ==> learn_hints.fin) accountR). Proof. solve_learnable. Qed. cpp.spec (lambda_call outer_lambda) from source inline. @@ -434,11 +443,11 @@ Section clients. \arg{to} "to" (Vptr to) \arg{i} "i" (Vint i) \persist{th} current_thread th - \prepost{gpool Nfrom Nto qfrom qto} - from |-> accountR gpool Nfrom qfrom ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nfrom)) ** - to |-> accountR gpool Nto qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)) + \prepost{Nfrom Nto qfrom qto} + from |-> accountR Nfrom qfrom ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nfrom)) ** + to |-> accountR Nto qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nto)) \post emp). Local Lemma call_with_continuation (A : mpred -> mpred) R Q : @@ -456,8 +465,8 @@ Section clients. iIntros (v1) "Hfrom_balance". (* The outer acquisition exposes from's balance; to retains its account and namespace. *) wname [accountR] "Hto". - iAssert (from |-> account_balanceR v1 ** to |-> accountR gpool Nto qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I + iAssert (from |-> account_balanceR v1 ** to |-> accountR Nto qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nto)))%I with "[$]" as "Hafter_from". iDestruct "Hafter_from" as "[? ?]". rewrite /balance_callback. iIntros (Qfrom) "HKfrom". go. @@ -478,8 +487,8 @@ Section clients. iApply "HKto". go. (* Returning from to.call restores its account and namespace resource. *) iAssert (from ,, _field "C::balance" |-> ulongR 1$m (trim 64 (v1 - i)) ** - to |-> accountR gpool Nto qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I + to |-> accountR Nto qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nto)))%I with "[$]" as "Hafter_to". iDestruct "Hafter_to" as "(Hfrom_balance & Hto & Hto_names)". iApply "HKfrom". iExists (trim 64 (v1 - i)). @@ -487,10 +496,10 @@ Section clients. { iDestruct "Hfrom_balance" as "?". go. } iIntros "(Hfrom & Hfrom_names)". (* Returning from from.call restores both accounts and namespace resources. *) - iAssert (from |-> accountR gpool Nfrom qfrom ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nfrom)) ** - to |-> accountR gpool Nto qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑Nto)))%I + iAssert (from |-> accountR Nfrom qfrom ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nfrom)) ** + to |-> accountR Nto qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑Nto)))%I with "[$Hfrom $Hfrom_names $Hto $Hto_names]" as "Hafter_from". iDestruct "Hafter_from" as "[? ?]". go. wname [bi_wand] "Hpost". @@ -511,11 +520,11 @@ Section clients. \arg{to} "to" (Vptr to) \arg{i} "i" (Vint i) \persist{th} current_thread th - \prepost{gpool qfrom qto} - from |-> accountR gpool from_namespace qfrom ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑from_namespace)) ** - to |-> accountR gpool to_namespace qto ** - MutexSets.my_mutexes gpool th (coPset.CoPset (↑to_namespace)) + \prepost{qfrom qto} + from |-> accountR from_namespace qfrom ** + MutexSets.my_mutexes th (coPset.CoPset (↑from_namespace)) ** + to |-> accountR to_namespace qto ** + MutexSets.my_mutexes th (coPset.CoPset (↑to_namespace)) \post emp ). @@ -538,7 +547,7 @@ Section clients. cpp.spec "main()" as main_spec from source with ( \persist{th} current_thread th - \prepost{gpool} MutexSets.my_mutexes gpool th (coPset.CoPset ⊤) + \prepost MutexSets.my_mutexes th (coPset.CoPset ⊤) \post[Vint 0] emp ). @@ -546,27 +555,27 @@ Section clients. Proof. verify_spec. let rec expose_names := - first [iDestruct select (MutexSets.my_mutexes gpool th _) as "Hnames" + first [iDestruct select (MutexSets.my_mutexes th _) as "Hnames" | progress go1; expose_names] in expose_names. go. - iExists gpool, from_namespace; go. - iExists gpool, to_namespace; go. + iExists from_namespace; go. + iExists to_namespace; go. have Hdisjoint := transfer_namespaces_disjoint. - iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th ⊤ + iDestruct (MutexSets.my_mutexes_alloc_mutex_name th ⊤ (↑from_namespace) ltac:(set_solver) with "Hnames") as "[Hnames Hfrom]". - iDestruct (MutexSets.my_mutexes_alloc_mutex_name gpool th + iDestruct (MutexSets.my_mutexes_alloc_mutex_name th (⊤ ∖ ↑from_namespace) (↑to_namespace) ltac:(set_solver) with "Hnames") as "[Hnames Hto]". iDestruct "Hfrom" as "?". iDestruct "Hto" as "?". go. iExists (1$m)%cQp, (1$m)%cQp. go. - iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th + iDestruct (MutexSets.my_mutexes_join_mutex_name th (⊤ ∖ ↑from_namespace) (↑to_namespace) ltac:(set_solver) with "[$]") as "Hnames". - iDestruct (MutexSets.my_mutexes_join_mutex_name gpool th ⊤ + iDestruct (MutexSets.my_mutexes_join_mutex_name th ⊤ (↑from_namespace) ltac:(set_solver) with "[$]") as "Hnames". iFrame "Hnames". diff --git a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v index 0dcf1ca1..f3ac8421 100644 --- a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v @@ -38,7 +38,6 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. gmap_viewR thread_idT (agreeR (leibnizO entry)). Record mutex_gname : Set := MkGname { - pool_gname : iprop.gname; token_gname : iprop.gname; share_gname : iprop.gname; holders_gname : iprop.gname; @@ -46,11 +45,9 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. inv_namespace : namespace; }. Definition gname : Set := mutex_gname. - Definition pool_name (g : gname) := g.(pool_gname). Definition smutex_inv_namespace (g : gname) := g.(inv_namespace). Class stateG `{Σ : cpp_logic} := { - #[global] sets_G :: MutexSets.G Σ; #[local] holders_own :: HasOwn (iPropI _Σ) holdersR; #[local] holders_upd :: HasOwnUpd (iPropI _Σ) holdersR; #[local] holders_valid :: HasOwnValid (iPropI _Σ) holdersR; @@ -77,9 +74,9 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. own g.(holders_gname) (gmap_view_frag th (DfracOwn 1) (to_agree (A := leibnizO entry) e) : holdersR). - Definition not_locked `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := - token g qt ** MutexSets.my_mutexes (pool_name g) th + token g qt ** MutexSets.my_mutexes th (CoPset $ ↑smutex_inv_namespace g). Definition locked `{Σ : cpp_logic, !G Σ} {σ : genv} (this : ptr) (g : gname) (th : thread_idT) (qt : cQp.t) : mpred := @@ -156,16 +153,16 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. Qed. End ghost_laws. - #[global] Instance not_locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance not_locked_timeless `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g th qt : Timeless (not_locked this g th qt). Proof. rewrite /not_locked. apply _. Qed. - #[global] Instance not_locked_exclusive `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance not_locked_exclusive `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g th : Exclusive1 (not_locked this g th). Proof. intros q1 q2. rewrite /not_locked. iIntros "[_ A] [_ B]". have Hoverlap : ~ ((↑smutex_inv_namespace g : coPset) ## ↑smutex_inv_namespace g). { have Hne := nclose_non_empty (smutex_inv_namespace g). set_solver. } - iDestruct (MutexSets.my_mutexes_exclusive _ _ _ _ Hoverlap + iDestruct (MutexSets.my_mutexes_exclusive _ _ _ Hoverlap with "[$A $B]") as %[]. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ} {σ : genv} @@ -230,11 +227,11 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. Definition max_readers : Z := 2147483647. #[global] Arguments max_readers /. - Definition deposits `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition deposits `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (M : reader_map) : mpred := [∗ map] th ↦ qr ∈ M, not_locked this g th qr.1. - Definition state `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (P : Qp -> mpred) (n : Z) : mpred := (* thread [th] is the writer *) (∃ th qt, [| n = -1 |] ** authority g {[th := (qt,None)]} ** @@ -245,14 +242,16 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. authority g (reader_entries M) ** share g s ** P s ** deposits this g M). #[global] Instance state_WeaklyObjective - `{Σ : cpp_logic, !G Σ} {σ : genv} this g P n + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g P n `{!forall q, Objective (P q)} : WeaklyObjective (state this g P n). Proof. rewrite /state /authority /share /deposits /not_locked /token. apply _. Qed. Section state_laws. - Context `{Σ : cpp_logic, !G Σ} {σ : genv}. + Context `{Σ : cpp_logic} {σ : genv}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!G Σ}. Lemma state_bounds this g P n : state this g P n |-- [| -1 <= n <= max_readers |]. @@ -411,12 +410,12 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. Qed. End state_laws. - Definition mutex_inv `{Σ : cpp_logic, !G Σ} {σ : genv} + Definition mutex_inv `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (g : gname) (P : Qp -> mpred) : mpred := ∃ n : Z, this ,, _field "MySharedMutex::sharers" |-> atomic.R "int" 1$m n ** state this g P n. - Definition R `{Σ : cpp_logic, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} + Definition R `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {HAS_THREADS : HasStdThreads Σ} {σ : genv} (g : gname) (q : cQp.t) (P : Qp -> mpred) : Rep := structR class_name q$m ** as_Rep (fun this => [| Fractional P /\ (forall qP, Objective (P qP)) |] ** @@ -425,7 +424,7 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. #[global] Hint Opaque token not_locked locked reader_locked R : sl_opacity typeclass_instances. #[only(cfractional,ascfractional,cfracvalid)] derive R. - #[global] Instance R_type_ptr `{Σ : cpp_logic, !G Σ} {σ : genv} + #[global] Instance R_type_ptr `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} {HAS_THREADS : HasStdThreads Σ} g q P : Observe (type_ptrR cpp_ty) (R g q P). Proof. rewrite /R /cpp_ty. apply _. Qed. @@ -435,7 +434,7 @@ Module CustomSharedMutexPreds <: SHARED_MUTEX_PREDS. WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. #[global] Instance mutex_inv_WeaklyObjective - `{Σ : cpp_logic, !G Σ} {σ : genv} this g P + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g P `{!forall qP, Objective (P qP)} : WeaklyObjective (mutex_inv this g P). Proof. rewrite /mutex_inv. apply _. Qed. @@ -455,8 +454,9 @@ Module custom_shared_mutex. Abbreviation BASE p := (p ,, _base "std::atomic" "std::__atomic_base"). Section with_Σ. - Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ, - !CustomSharedMutexPreds.G Σ}. + Context `{Σ : cpp_logic, σ : genv, HAS_THREADS : !HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!CustomSharedMutexPreds.G Σ}. Context `{MOD : source ⊧ σ}. cpp.spec "MySharedMutex::MySharedMutex()" as ctor_spec with @@ -767,10 +767,10 @@ Module custom_shared_mutex. iMod (own_alloc (gmap_view_auth (DfracOwn 1) ∅ : holdersR)) as (gh) "A". { apply gmap_view_auth_valid. } iMod (cinv_alloc ⊤ N - (mutex_inv this (MkGname pool gt gs gh 1%positive N) P) + (mutex_inv this (MkGname gt gs gh 1%positive N) P) with "[L P Share A]") as (gi) "[#CI CO]"; last first. - - iModIntro. go. iExists (MkGname pool gt gs gh gi N). - rewrite /IR /R _at_sep _at_as_Rep /token /pool_name /smutex_inv_namespace /=. + - iModIntro. go. iExists (MkGname gt gs gh gi N). + rewrite /IR /R _at_sep _at_as_Rep /token /smutex_inv_namespace /=. iFrame "CI S CO T". auto. - iNext. rewrite /mutex_inv /state. iExists 0%Z. iFrame "L". iRight. iExists ∅, 1%Qp. diff --git a/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v b/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v index 0a357713..ac8679f7 100644 --- a/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/shared_mutex/test_cpp_proof.v @@ -5,21 +5,22 @@ Require Import skylabs.brick.libstdcpp.test.shared_mutex.test_cpp. Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!shared_mutex.G Σ}. cpp.spec "test_shared_mutex()" as test_shared_mutex_spec from source with ( \persist{thr} current_thread thr - \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) \post emp). Lemma test_shared_mutex_ok : verify[source] "test_shared_mutex()". Proof. verify_spec; go. - iExists pool, (nroot .@@ "test_shared_mutex"), (const emp); go. + iExists (nroot .@@ "test_shared_mutex"), (const emp); go. rename t into g. iDestruct (MutexSets.my_mutexes_alloc_mutex_name - (shared_mutex.pool_name g) thr ⊤ + thr ⊤ (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". wname [shared_mutex.token] "Htoken". @@ -37,7 +38,7 @@ Section with_cpp. iEval (rewrite /shared_mutex.not_locked) in "Hnotlocked". iDestruct "Hnotlocked" as "[Htoken Hname]". iDestruct (MutexSets.my_mutexes_join_mutex_name - (shared_mutex.pool_name g) thr ⊤ + thr ⊤ (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) with "[Hrest Hname]") as "Hnames"; first iFrame. go $usenamed=true. @@ -45,17 +46,17 @@ Section with_cpp. cpp.spec "test_shared_mutex_shared()" as test_mutex_shared_spec from source with ( \persist{thr} current_thread thr - \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) \post emp). Lemma test_shared_mutex_shared : verify[source] "test_shared_mutex_shared()". Proof. verify_spec; go. - iExists pool, (nroot .@@ "test_shared_mutex_shared"), (const emp); go. + iExists (nroot .@@ "test_shared_mutex_shared"), (const emp); go. rename t into g. iDestruct (MutexSets.my_mutexes_alloc_mutex_name - (shared_mutex.pool_name g) thr ⊤ + thr ⊤ (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". wname [shared_mutex.token] "Htoken". @@ -68,7 +69,7 @@ Section with_cpp. iEval (rewrite /shared_mutex.not_locked) in "Hnotlocked". iDestruct "Hnotlocked" as "[Htoken Hname]". iDestruct (MutexSets.my_mutexes_join_mutex_name - (shared_mutex.pool_name g) thr ⊤ + thr ⊤ (↑shared_mutex.smutex_inv_namespace g) ltac:(set_solver) with "[Hrest Hname]") as "Hnames"; first iFrame. go $usenamed=true. From 0a1b844a19176a24b107d63a4790471158052908 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 13 Oct 2026 02:08:23 +0000 Subject: [PATCH 090/109] generic [lock_guard.v] spec --- .../proof/mutex/spec/lock_guard.v | 197 ++++++++---------- 1 file changed, 82 insertions(+), 115 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index c1583c05..99ea3081 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -1,128 +1,95 @@ Require Import skylabs.auto.cpp.proof. -Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Require Export skylabs.brick.libstdcpp.runtime.pred. - Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. +Require Import skylabs.brick.libstdcpp.mutex.inc_hpp_templates. - -Import linearity. - -Section TO_UPSTREAM. - Lemma cQp_mut_add q1 q2 : - (q1 + q2)$m%cQp = (q1$m + q2$m)%cQp. - Proof. done. Qed. -End TO_UPSTREAM. - -Module lock_guard. - +Require Export skylabs.brick.libstdcpp.runtime.pred. +Require Import skylabs.brick.libstdcpp.mutex.spec.prelude. +Require Import skylabs.brick.libstdcpp.mutex.requirements. + +(* Generic specs for "lock_guard". *) +NES.Begin lock_guard. + Record M {T : Type} : Type := Mk + { mutex_ptr : ptr + ; mutex_q : Qp + ; mutex_m : T }. + #[global] Arguments M _ : clear implicits. + #[global] Arguments Mk {_} _ _ _. + #[only(lens)] derive M. + + #[global] Hint Opaque mutex_ptr : sl_opacity. + + (* Wrapper of a basic lockable object with resource predicate [mutexR]. *) sl.lock - Definition R `{Σ : cpp_logic, !HasStdThreads Σ, !mutex.G Σ} {σ : genv} - (mp : ptr * mutex.gname * cQp.t) (q : cQp.t) (P : mpred) : Rep := - structR "std::lock_guard" q ** - let '(mp, g, q') := mp in - _field "std::lock_guard::_M_device" |-> refR<"std::mutex"> q mp ** - pureR ( - mp |-> mutex.R g (q * q')$m P). - - #[only(type_ptr)] derive R. - #[only(lazy_unfold)] derive R. + Definition R + `{Σ : cpp_logic} {σ : genv} ty {T} mutexR `{!BasicLockable ty (T:=T) mutexR} + (q : cQp.t) (m : M T) : Rep := + let lgty := "std::lock_guard" .<< Atype ty >> in + structR lgty q ** + _field (lgty .:: Nid "_M_device") |-> refR q m.(mutex_ptr) ** + pureR (m.(mutex_ptr) |-> mutexR (cQp.scale m.(mutex_q) q) m.(mutex_m)). Section with_RepFor. Import rep.RepFor. Import RepScheme. - #[global] Instance repfor `{Σ : cpp_logic, !HasStdThreads Σ, !mutex.G Σ} {σ : genv} : - rep.RepFor.C "std::lock_guard" - [ArgType.Constant _; ArgType.CFrac; ArgType.Constant _] - R := {}. + #[global] Instance repfor `{Σ : cpp_logic} {σ : genv} ty {T} mutexR `{!BasicLockable ty (T:=T) mutexR} : + rep.RepFor.C (Tnamed $ "std::lock_guard" .<< Atype ty >>) + [ArgType.CFrac; ArgType.Constant _] (R ty mutexR) := {}. End with_RepFor. - (** - These automated proofs fail, so we prove it by hand. - [R_cfrac] does not seem too useful (why ever split a lock guard?), but let's - prove it anyway to test our infrastructure. *) - Fail #[only(cfractional,cfracvalid,ascfractional)] derive R. - - #[only(cfracvalid)] derive R. - -Section with_cpp. - Context `{Σ : cpp_logic, σ : genv}. - Context {HAS_THREADS : HasStdThreads Σ}. - Context `{!mutex.G Σ}. - - #[global] Instance R_learn : - Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) lock_guard.R) := - ltac:(solve_learnable). - - Set Printing Coercions. - - Section with_R_cfrac'. - #[local] Instance R_cfrac' g q' P : - CFractional (λ q, mutex.R g (cQp.frac q * q')$m P). - (* Proof. - intros q1 q2. - rewrite -(cfractional (P := λ q, mutex.R _ q _)). - rewrite -cQp_mut_add. - rewrite -Qp.mul_add_distr_r. - Succeed done. - by rewrite -cQp.frac_add. - Restart. - *) - Proof. - intros q1 q2. - rewrite cQp.frac_add. - rewrite Qp.mul_add_distr_r. - rewrite cQp_mut_add. - by rewrite (cfractional (P := λ q, mutex.R g q P)). - Qed. - - #[global] Instance R_cfrac mp : CFractional1 (R mp). - Proof. rewrite R.unlock. apply _. Qed. - End with_R_cfrac'. - - Fail #[only(ascfractional)] derive R. - #[global] Instance R_as_cfrac mp : AsCFractional1 (R mp). - Proof. solve_as_cfrac. Qed. - - cpp.spec "std::lock_guard::lock_guard(std::mutex &)" as ctor_spec from source with ( - \this this - \arg{mp} "m" (Vptr mp) - \persist{thr} current_thread thr - \pre{g q P} mp |-> mutex.R g q P - \pre{qt} mutex.not_locked mp g thr qt - \post - this |-> R (mp, g, q) 1$m P ** - P ** mutex.locked mp g thr qt + Module R_unfold. + #[only(lazy_unfold(export))] derive R. + End R_unfold. + + Section with_cpp. + Context `{Σ : cpp_logic} {σ : genv}. + Context ty {mutexT mutexR} `{!BasicLockable ty (T:=mutexT) mutexR}. + + #[only(type_ptr)] derive R. + #[only(cfracvalid)] derive R. + + Section with_cfrac. + Context `{CFrac : !CFractional1 mutexR}. + #[local] Set Default Proof Using "CFrac". + + #[global] Instance R_cfrac : CFractional1 (R ty mutexR). + Proof. rewrite R.unlock. apply _. Qed. + + #[global] Instance R_as_cfrac : AsCFractional1 (R ty mutexR). + Proof. solve_as_cfrac. Qed. + + Lemma mutex_borrow mp m (this : ptr) (q1 q2 : Qp) : + this |-> R ty mutexR 1$m (Mk mp (q1 + q2) m) |-- + mp |-> mutexR q1$m m ** + this |-> R ty mutexR 1$m (Mk mp q2 m). + Proof. + rewrite R.unlock /= !cQp.scale_mut !(right_id_L 1%Qp Qp.mul). + change ((q1 + q2)$m)%cQp with (q1$m + q2$m)%cQp. + rewrite (cfractional (P := λ q, mutexR q m)). + iIntros "($ & $ & $ & $)". + Qed. + End with_cfrac. + + #[local] Abbreviation R := (R ty (T:=mutexT) mutexR). + + #[global] Instance R_learn : LearnEqF1 R := ltac:(solve_learnable). + + cpp.spec "std::lock_guard<$ty>::lock_guard($ty&)" as ctor_spec from source templates templates ( + \\with + \this this + \arg{mp} "" (Vptr mp) + \pre{q m} mp |-> mutexR q$m m + \pre{K} do_lock ty mp m K + \post + this |-> R 1$m (Mk mp q m) ** K ). - cpp.spec "std::lock_guard::~lock_guard()" as dtor_spec from source with ( - \this this - \pre{mp g q P} this |-> R (mp, g, q) 1$m P - \persist{thr} current_thread thr - \pre{qt} mutex.locked mp g thr qt - \pre ▷P - \post - mutex.not_locked mp g thr qt ** - mp |-> mutex.R g q P - ). - - Section with_prelude. - - Import skylabs.auto.cpp.prelude.proof. - - Lemma mutex_borrow mp g P (this : ptr) (q1 q2 : Qp) : - this |-> R (mp, g, ((q1 + q2)$m)%cQp) 1$m P |-- - mp |-> mutex.R g q1$m P ** - this |-> R (mp, g, q2$m)%cQp 1$m P. - Proof. - rewrite R.unlock. - work. - iDestruct select (mp |-> mutex.R g _ P) as "[??]". - (* Unnecessary with our prelude. *) - (* rewrite !left_id_L. *) - work. - Qed. - End with_prelude. - -End with_cpp. -End lock_guard. + cpp.spec "std::lock_guard<$ty>::~lock_guard()" as dtor_spec from source templates templates ( + \\with + \this this + \pre{m} this |-> R 1$m m + \pre{K} do_unlock ty m.(mutex_ptr) m.(mutex_m) K + \post K ** m.(mutex_ptr) |-> mutexR m.(mutex_q)$m m.(mutex_m) + ). + End with_cpp. +NES.End lock_guard. From 26964a8398c78ba2bb45934d0aa6a29b2a8c8f2a Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 13 Oct 2026 02:21:23 +0000 Subject: [PATCH 091/109] fix files that depend on lock_guard --- .../proof/mutex/proof/lock_guard.v | 20 +++++++++++++------ .../test/mutex/guard_recursive_cpp_proof.v | 20 +++++++++---------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index 718b9d50..4d8b1ff3 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -9,9 +9,11 @@ Import linearity. Section with_cpp. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!mutex.G Σ}. Import lock_guard. + Import R_unfold. (* TODO: not ideal *) #[global] Instance UNSAFE_R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, @@ -19,24 +21,30 @@ Section with_cpp. Proof. solve_learnable. Qed. #[local] Hint Resolve fractional.UNSAFE_read_prim_learn : sl_opacity. + #[local] Instance: `{SplitRecord (M T)} := {}. - Lemma ctor_ok : verify[source] ctor_spec. + (* std::mutex ctor implies lock_guard ctor *) + Lemma ctor_ok : + denoteModule source ** + mutex.lock_spec + |-- lock_guard.ctor_spec "std::mutex" source. Proof. verify_spec. go. - iExists (mutex.locked mp g thr qt ** P), qt. + iExists K, t. go with br_erefl. - by rewrite (left_id_L 1%Qp Qp.mul). + by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). Qed. - Lemma dtor_ok : verify[source] dtor_spec. + (* std::mutex dtor implies lock_guard dtor *) + Lemma dtor_ok : verify[source] "std::lock_guard::~lock_guard()". Proof. verify_spec. rewrite !R.unlock. go. - iExists (mutex.not_locked mp g thr qt), qt. + iExists K, t. go with br_erefl. - by rewrite (left_id_L 1%Qp Qp.mul). + by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). Qed. End with_cpp. diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index cb03062d..803f3f26 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -84,7 +84,7 @@ Definition P `{Σ : cpp_logic, σ : genv} (this : ptr) : TT -t> mpred := sl.lock Definition CR `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ} - `{!std_recursive_mutex.G Σ} + `{MUTEX_SETS : !mutex_set_G Σ, !std_recursive_mutex.G Σ} (γ : std_recursive_mutex.gname) (q : cQp.t) := structR "C" q ** as_Rep (fun this : ptr => @@ -97,6 +97,7 @@ Definition CR Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!std_recursive_mutex.G Σ}. #[local] Instance: `{Objective1 (@tele_app TT (fun _ => mpred) (P p))}. @@ -112,22 +113,21 @@ Section with_cpp. cpp.spec "test_one_answer()" from source with ( \persist{thr} current_thread thr - \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) \post[Vint 42] emp ). cpp.spec "test_other_answer()" from source with ( \persist{thr} current_thread thr - \prepost{pool} MutexSets.my_mutexes pool thr (coPset.CoPset ⊤) + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) \post[Vint 42] emp ). (** Initialize the member lock but do not acquire it. *) cpp.spec "C::C()" from source as C_ctor_spec with ( \this this - \pre{pool N} emp + \pre{N} emp \post Exists γ, - [| std_recursive_mutex.pool_name γ = pool /\ - std_recursive_mutex.rmutex_inv_namespace γ = N |] ** + [| std_recursive_mutex.rmutex_inv_namespace γ = N |] ** this |-> CR γ 1$m ** std_recursive_mutex.token γ 1$m ). @@ -139,7 +139,7 @@ Section with_cpp. Proof. verify_shift. go. - iExists pool, N, TT, (P this), (mk 0); go. + iExists N, TT, (P this), (mk 0); go. rewrite (* Ugh *) -bi.later_intro. rewrite {1}P.unlock. go. @@ -169,9 +169,9 @@ Section with_cpp. verify[source] "test_one_answer()". Proof. verify_spec; go. - iExists pool, (nroot .@@ "guard_recursive"); go. + iExists (nroot .@@ "guard_recursive"); go. iDestruct (MutexSets.my_mutexes_alloc_mutex_name - (std_recursive_mutex.pool_name t) thr ⊤ + thr ⊤ (↑std_recursive_mutex.rmutex_inv_namespace t) ltac:(set_solver) with "[$]") as "[Hrest Hname]". wname [std_recursive_mutex.token] "Htoken". @@ -220,7 +220,7 @@ Section with_cpp. /std_recursive_mutex.not_locked) in "Hacquireable". iDestruct "Hacquireable" as "[Htoken Hname]". iDestruct (MutexSets.my_mutexes_join_mutex_name - (std_recursive_mutex.pool_name γ) thr ⊤ + thr ⊤ (↑std_recursive_mutex.rmutex_inv_namespace γ) ltac:(set_solver) with "[Hrest Hname]") as "Hnames"; first iFrame. iFrame "Htoken". go $usenamed=true. From 0db3eef855b3968e57ea1a97c88e4ba3fe4e3f8b Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 13 Oct 2026 02:31:42 +0000 Subject: [PATCH 092/109] fix comments --- rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index 4d8b1ff3..1f965da0 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -23,7 +23,7 @@ Section with_cpp. #[local] Hint Resolve fractional.UNSAFE_read_prim_learn : sl_opacity. #[local] Instance: `{SplitRecord (M T)} := {}. - (* std::mutex ctor implies lock_guard ctor *) + (* std::mutex::lock() spec implies lock_guard ctor *) Lemma ctor_ok : denoteModule source ** mutex.lock_spec @@ -36,7 +36,7 @@ Section with_cpp. by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). Qed. - (* std::mutex dtor implies lock_guard dtor *) + (* std::mutex::unlock() spec implies lock_guard dtor *) Lemma dtor_ok : verify[source] "std::lock_guard::~lock_guard()". Proof. verify_spec. From 14561bd84558a7e3cb7ac8881d1e5a3e0d6c44db Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Tue, 13 Oct 2026 03:13:40 +0000 Subject: [PATCH 093/109] plausible generic [scoped_lock.v] specs --- .../proof/mutex/spec/scoped_lock.v | 128 ++++++++++-------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v index 1cc666c7..0e30fa45 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/scoped_lock.v @@ -1,65 +1,85 @@ -Require Import skylabs.auto.cpp.prelude.proof. -Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. +Require Import skylabs.auto.cpp.proof. +Require Import skylabs.cpp.spec.specify. Require Export skylabs.brick.libstdcpp.runtime.pred. -Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. -Export lock_ghost2.mutex_set_G (mutex_set_G). +Require Import skylabs.brick.libstdcpp.mutex.spec.prelude. +Require Import skylabs.brick.libstdcpp.mutex.requirements. + +(* Generic specs for "scoped_lock". *) +NES.Begin scoped_lock. + Record M {T : Type} : Type := Mk + { mutex_ptr : ptr + ; mutex_q : Qp + ; mutex_m : T }. + #[global] Arguments M _ : clear implicits. + #[global] Arguments Mk {_} _ _ _. + #[only(lens)] derive M. + + #[global] Hint Opaque mutex_ptr : sl_opacity. + + (* We only write specs in the case of two mutexes in a scoped lock. + More mutexes is not conceptually more difficult. *) + Parameter R : forall + `{Σ : cpp_logic} {σ : genv} ty1 ty2 {T1 T2} mutexR1 mutexR2 + `{!BasicLockable ty1 (T:=T1) mutexR1, !Lockable ty1 mutexR1, + !BasicLockable ty2 (T:=T2) mutexR2, !Lockable ty2 mutexR2}, + cQp.t -> M T1 * M T2 -> Rep. -Module scoped_lock. Section with_cpp. - Context `{Σ : cpp_logic}. + Context `{Σ : cpp_logic} {σ : genv}. + Context (ty1 ty2 : type) {mutexT1 mutexT2 : Type}. + Context {mutexR1 : cQp.t -> mutexT1 -> Rep} {mutexR2 : cQp.t -> mutexT2 -> Rep}. + Context `{!BasicLockable ty1 (T:=mutexT1) mutexR1, !Lockable ty1 mutexR1}. + Context `{!BasicLockable ty2 (T:=mutexT2) mutexR2, !Lockable ty2 mutexR2}. - Parameter R : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, - cQp.t -> list (ptr * mutex.gname * Qp * cQp.t * mpred) -> Rep. + #[local] Definition scoped_lock_type := + Tnamed ("std::scoped_lock" .<< Apack [Atype ty1; Atype ty2] >>). + #[only(type_ptr=scoped_lock_type,cfracvalid)] derive R. - #[only(type_ptr="std::scoped_lock")] derive R. - #[only(cfractional,ascfractional,cfracvalid)] derive R. + Section with_cfrac. + Context `{CFrac1 : !CFractional1 mutexR1, CFrac2 : !CFractional1 mutexR2}. + #[local] Set Default Proof Using "CFrac1 CFrac2". - Section with_threads. - Context {σ : genv}. - Context `{HAS_THREADS : !HasStdThreads Σ}. - Context `{MUTEX_SETS : !mutex_set_G Σ}. - Context `{!mutex.G Σ}. + #[only(cfractional,ascfractional)] derive R. + End with_cfrac. - #[global] Instance: LearnEqF1 R := ltac:(solve_learnable). + #[local] Abbreviation R := (R ty1 ty2 mutexR1 mutexR2). - cpp.spec - "std::scoped_lock<...>::scoped_lock(std::mutex&, std::mutex&)" - (* "std::scoped_lock::scoped_lock(std::mutex&, std::mutex&)" *) - as ctor_spec from source with ( - \this this - \persist{thr} current_thread thr - \arg{mp1} "" (Vptr mp1) - \pre{g1 q1 qt1 P1} mp1 |-> mutex.R g1 q1$m P1 - \pre mutex.not_locked mp1 g1 thr qt1 - \arg{mp2} "" (Vptr mp2) - \pre{g2 q2 qt2 P2} mp2 |-> mutex.R g2 q2$m P2 - \pre mutex.not_locked mp2 g2 thr qt2 - \post - this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] ** - P1 ** mutex.locked mp1 g1 thr qt1 ** - P2 ** mutex.locked mp2 g2 thr qt2 - ). + #[global] Instance R_learn : LearnEqF1 R := ltac:(solve_learnable). - cpp.spec "std::scoped_lock<...>::~scoped_lock()" - as dtor_spec from source with ( - \this this - \persist{thr} current_thread thr - \pre{ - mp1 mp2 - g1 q1 qt1 P1 - g2 q2 qt2 P2 - } - this |-> R 1$m [ (mp1, g1, q1, qt1, P1); (mp2, g2, q2, qt2, P2)] - \pre |> P1 - \pre |> P2 - \pre mutex.locked mp1 g1 thr qt1 - \pre mutex.locked mp2 g2 thr qt2 - \post - mp1 |-> mutex.R g1 q1$m P1 ** mutex.not_locked mp1 g1 thr qt1 ** - mp2 |-> mutex.R g2 q2$m P2 ** mutex.not_locked mp2 g2 thr qt2 - ). - End with_threads. - End with_cpp. + (* The generated template metadata does not expand parameter packs, so + register the two-mutex signatures explicitly. *) + #[local] Abbreviation scoped_lock_name := + ("std::scoped_lock" .<< Apack [Atype ty1; Atype ty2] >>) (only parsing). + + Definition ctor_spec : mpred := + specify.template.ctor scoped_lock_name [Tref ty1; Tref ty2] $ + \this this + \arg{mp1} "" (Vptr mp1) + \pre{q1 m1} mp1 |-> mutexR1 q1$m m1 + \pre{K1} do_lock ty1 mp1 m1 K1 + \arg{mp2} "" (Vptr mp2) + \pre{q2 m2} mp2 |-> mutexR2 q2$m m2 + \pre{K2} do_lock ty2 mp2 m2 K2 + \post + this |-> R 1$m (Mk mp1 q1 m1, Mk mp2 q2 m2) ** K1 ** K2. + #[global] Hint Opaque ctor_spec : sl_opacity. + #[global] Arguments ctor_spec : simpl never. + Definition SpecFor_ctor_spec := RegisterSpec ctor_spec. + #[global] Existing Instance SpecFor_ctor_spec. -End scoped_lock. + Definition dtor_spec : mpred := + specify.template.dtor scoped_lock_name $ + \this this + \pre{m1 m2} this |-> R 1$m (m1, m2) + \pre{K1} do_unlock ty1 m1.(mutex_ptr) m1.(mutex_m) K1 + \pre{K2} do_unlock ty2 m2.(mutex_ptr) m2.(mutex_m) K2 + \post K1 ** K2 ** + m1.(mutex_ptr) |-> mutexR1 m1.(mutex_q)$m m1.(mutex_m) ** + m2.(mutex_ptr) |-> mutexR2 m2.(mutex_q)$m m2.(mutex_m). + #[global] Hint Opaque dtor_spec : sl_opacity. + #[global] Arguments dtor_spec : simpl never. + Definition SpecFor_dtor_spec := RegisterSpec dtor_spec. + #[global] Existing Instance SpecFor_dtor_spec. + End with_cpp. +NES.End scoped_lock. From 5c4c5c6d2ca3564817cc15a254fc8d5058fc3938 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 05:19:21 +0000 Subject: [PATCH 094/109] remove the second, extraneous [my_mutexes] from mutex implementation proof; require mutex namespace to not be [nroot] --- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 50 ++++++++--- .../proof/mutex/spec/recursive_mutex.v | 1 + .../test/mutex/custom_mutex_hpp_proof.v | 90 ++++++++++--------- .../mutex/custom_recursive_mutex_hpp_proof.v | 53 ++++++----- .../test/mutex/double_incr_cpp_proof.v | 2 +- .../test/mutex/guard_recursive_cpp_proof.v | 3 + .../mutex/transfer_with_mutex_cpp_proof.v | 5 ++ 7 files changed, 126 insertions(+), 78 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index d712b0da..d823c111 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -24,6 +24,9 @@ Module Type MUTEX_PREDS. Parameter gname : Set. Parameter cpp_ty : type. Parameter mutex_inv_namespace : gname -> namespace. + (* The invariant namespace cannot be [nroot] for practical purposes. + TODO maybe other mutex preds should also have this? *) + Axiom mutex_inv_namespace_not_root : forall g, mutex_inv_namespace g <> nroot. Parameter G : forall `{Σ : cpp_logic}, Type. Existing Class G. @@ -132,18 +135,27 @@ Module Type MUTEX_PREDS. implementation. *) Axiom init_R : forall (this : ptr) (old : gname) (N : namespace) (P : mpred), - WeaklyObjective P -> + WeaklyObjective P -> + N <> nroot -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- (|={⊤}=> ∃ g, [| mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). - (** FIXME do we really need this? *) - Axiom locked_my_mutexes_exclusive : forall - `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this g th qt, - locked this g th qt ** + (** For any [h1],[h2] ∈ {[locked _ _ th _], [not_locked _ _ th _]}, we + would like [h1 ** h2 ⊢ False] because there should be at most one such + handle for thread [th]. We already have [locked_exclusive] and + [not_locked_exclusive], so we also want [locked ** not_locked ⊢ False]. + But here we require something more specific because it is easier for the + implementation proof: we don't need the whole [not_locked] but just the + [my_mutex] part, and we also need to prove this by opening the mutex + invariant in [R], thus the [this |-> R ...] in the LHS and the update on + the RHS. *) + Axiom locked_my_mutexes_exclusive : forall (this : ptr) g th q qt P E, + ↑mutex_inv_namespace g ⊆ E -> + this |-> R g q P ** locked this g th qt ** MutexSets.my_mutexes th - (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. + (coPset.CoPset $ ↑mutex_inv_namespace g) |-- (|={E}=> False). End with_cpp. End MUTEX_PREDS. @@ -160,19 +172,35 @@ Section with_cpp. Context {HAS_THREADS : HasStdThreads Σ}. - Lemma locked_not_locked_exclusive (this : ptr) g th qt qt' : - locked this g th qt ** not_locked this g th qt' |-- False. + (** Choose a sibling namespace for an auxiliary invariant. *) + Lemma mutex_inv_namespace_disjoint g : + ∃ No : namespace, mutex_inv_namespace g ## No. Proof. - rewrite /not_locked. iIntros "[HL [_ Hnames]]". - iApply (locked_my_mutexes_exclusive with "[$HL $Hnames]"). + have HN := mutex_inv_namespace_not_root g. + destruct (mutex_inv_namespace g) as [[|p ns]]; first done. + exists (Namespace ns .@ Pos.succ p). + have -> : Namespace (p :: ns) = Namespace ns .@ p. + { by rewrite namespaces.ndot_unseal. } + apply ndot_ne_disjoint. lia. + Qed. + + Lemma locked_not_locked_exclusive (this : ptr) g th q qt qt' P E : + ↑mutex_inv_namespace g ⊆ E -> + this |-> R g q P ** locked this g th qt ** not_locked this g th qt' |-- + (|={E}=> False). + Proof. + rewrite /not_locked. iIntros (Hmask) "(HR & HL & _ & Hnames)". + iApply (locked_my_mutexes_exclusive with "[$HR $HL $Hnames]"). done. Qed. (** The guarded predicate must be weakly objective for invariant allocation, which R likely has. *) Definition ctor_spec (this : ptr) : WpSpec mpred val val := - (\pre{P} ▷P + (\pre{N P} ▷P + \require N <> nroot \require WeaklyObjective P \post |={⊤}=> Exists g, + [| mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). Definition dtor_spec : ptr -> WpSpec mpred val val := diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 78edcc42..97830311 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -256,6 +256,7 @@ Module recursive_mutex_spec (T : MutexCPPName) Definition ctor_spec : ptr -> WpSpec mpred val val := (\this this \pre{N TT P xs} |> @tele_app TT (fun _ => mpred) P xs + \require N <> nroot \require ∀ xs, Objective (@tele_app TT (fun _ => mpred) P xs) \post Exists g, [| rmutex_inv_namespace g = N |] ** diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index bfd97616..8a0cac74 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -25,6 +25,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. token_gname : iprop.gname; owner_gname : iprop.gname; state_namespace : namespace; + state_namespace_not_root : state_namespace <> nroot; }. Definition class_name : name := "MyMutex"%cpp_name. @@ -38,8 +39,8 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition mutex_inv_namespace (g : gname) : namespace := g.(state_namespace). - Definition held_namespace (g : state_gname) : namespace := - g.(state_namespace) .@ "held". + Lemma mutex_inv_namespace_not_root g : mutex_inv_namespace g <> nroot. + Proof. exact g.(state_namespace_not_root). Qed. Class stateG `{Σ : cpp_logic} := { #[global] tokens_G :: MutexTokens.G Σ; @@ -61,11 +62,10 @@ Module CustomMutexPreds <: MUTEX_PREDS. token γ q ** MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace γ). - Definition owner_token `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} + Definition owner_token `{Σ : cpp_logic, !G Σ} (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := MutexTokens.given_token γ.(token_gname) q ** - OwnerTid.owner_tid_frag γ.(owner_gname) (Some th) ** - MutexSets.my_mutexes th (coPset.CoPset $ ↑held_namespace γ). + OwnerTid.owner_tid_frag γ.(owner_gname) (Some th). #[global] Instance state_token_fractional `{Σ : cpp_logic, !G Σ} γ : CFractional (state_token γ). @@ -79,27 +79,23 @@ Module CustomMutexPreds <: MUTEX_PREDS. Proof. rewrite /state_token. apply _. Qed. #[global] Instance owner_token_timeless - `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ th q : Timeless (owner_token γ th q). + `{Σ : cpp_logic, !G Σ} γ th q : Timeless (owner_token γ th q). Proof. rewrite /owner_token. apply _. Qed. #[global] Instance owner_token_exclusive - `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ : Exclusive2 (owner_token γ). + `{Σ : cpp_logic, !G Σ} γ : Exclusive2 (owner_token γ). Proof. intros th1 th2 q1 q2. rewrite /owner_token. - iIntros "(_ & F1 & _) (_ & F2 & _)". + iIntros "[_ F1] [_ F2]". iDestruct (OwnerTid.owner_tid_frag_exclusive with "F1 F2") as %[]. Qed. - (** While held, the namespace permission is split between the invariant - and [owner_token]. The held part makes a second registration by the - same thread contradictory without opening this invariant. Unlock - rejoins the parts. While free, both owner fragments are in the invariant. *) Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (γ : state_gname) (b : bool) : mpred := (if b then ∃ th, OwnerTid.owner_tid_auth γ.(owner_gname) (Some th) ** MutexSets.my_mutexes th - (coPset.CoPset $ ↑γ.(state_namespace) ∖ ↑held_namespace γ) ** + (coPset.CoPset $ ↑γ.(state_namespace)) ** MutexTokens.token_not_full γ.(token_gname) else ∃ owner, OwnerTid.owner_tid_auth γ.(owner_gname) owner ** @@ -116,13 +112,13 @@ Module CustomMutexPreds <: MUTEX_PREDS. Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!G Σ}. - Lemma alloc_state (N : namespace) : + Lemma alloc_state (N : namespace) (HN : N <> nroot) : ⊢ |==> ∃ γ, [| γ.(state_namespace) = N |] ** state_token γ 1$m ** state γ false. Proof. iMod MutexTokens.alloc as (gt) "[T GT]". iMod (OwnerTid.alloc None) as (go) "[OA OF]". - iModIntro. iExists (MkStateGname gt go N). + iModIntro. iExists (MkStateGname gt go N HN). iSplit; first done. rewrite /state_token /state /=. iFrame "T". iExists None. iFrame "OA OF". @@ -139,10 +135,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) with "[$OA $OF]") as "[OA OF]". - iDestruct (MutexSets.my_mutexes_alloc_mutex_name th - (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) - with "Sets") as "[Rest Held]". - iModIntro. iFrame "GT OF Held". iExists th. iFrame. + iModIntro. iFrame "GT OF". iExists th. iFrame. Qed. Lemma state_unlock {σ : genv} this (γ : gname) th (q : cQp.t) : @@ -150,21 +143,18 @@ Module CustomMutexPreds <: MUTEX_PREDS. (|==> state γ false ** not_locked this γ th q). Proof. rewrite /state /owner_token /not_locked /token /state_token /mutex_inv_namespace. - iIntros "[State (GT & OF & Held)]". + iIntros "[State [GT OF]]". iDestruct "State" as (owner) "(OA & Sets & Balance)". iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. injection Heq as ->. iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". - iDestruct (MutexSets.my_mutexes_join_mutex_name th - (↑γ.(state_namespace)) (↑held_namespace γ) ltac:(rewrite /held_namespace; solve_ndisj) - with "[$Sets $Held]") as "Sets". iModIntro. iFrame "Sets T". iExists (Some th). iFrame. Qed. Lemma unlocked_owner_token γ th q : state γ false ** owner_token γ th q |-- False. Proof. - rewrite /state /owner_token. iIntros "[State (_ & OF & _)]". + rewrite /state /owner_token. iIntros "[State [_ OF]]". iDestruct "State" as (owner) "(_ & OF0 & _)". iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. @@ -231,21 +221,6 @@ Module CustomMutexPreds <: MUTEX_PREDS. this γ th q : WeaklyObjective (locked this γ th q). Proof. rewrite /locked /owner_token /MutexTokens.given_token. apply _. Qed. - Lemma locked_my_mutexes_exclusive `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} - this g th qt : - locked this g th qt ** - MutexSets.my_mutexes th - (coPset.CoPset $ ↑mutex_inv_namespace g) |-- False. - Proof. - rewrite /locked /owner_token /mutex_inv_namespace. - iIntros "[(_ & _ & _ & Held) Names]". - iApply (MutexSets.my_mutexes_exclusive with "[$Held $Names]"). - have Hnonempty := nclose_non_empty (held_namespace g). - have Hsub : (↑held_namespace g : coPset) ⊆ ↑g.(state_namespace). - { rewrite /held_namespace. solve_ndisj. } - set_solver. - Qed. - Section with_Σ. Context `{Σ : cpp_logic}. Context `{MUTEX_SETS : !mutex_set_G Σ}. @@ -276,9 +251,36 @@ Module CustomMutexPreds <: MUTEX_PREDS. Context {σ : genv} {HAS_THREADS : HasStdThreads Σ}. + Lemma locked_my_mutexes_exclusive (this : ptr) g th q qt P E : + ↑mutex_inv_namespace g ⊆ E -> + this |-> R g q P ** locked this g th qt ** + MutexSets.my_mutexes th + (coPset.CoPset $ ↑mutex_inv_namespace g) |-- (|={E}=> False). + Proof. + iIntros (Hmask) "(HR & Locked & Names)". + iEval (rewrite /R _at_sep _at_as_Rep) in "HR". + iDestruct "HR" as "(_ & #CI & CO)". + iInv (mutex_inv_namespace g) as "[Inv CO]" "Hclose". + iEval (rewrite /mutex_inv) in "Inv". + iDestruct "Inv" as (b) "(_ & State & _)". + iEval (rewrite /locked /owner_token) in "Locked". + iDestruct "Locked" as "(_ & _ & OF)". + iEval (rewrite /state) in "State". + destruct b. + - iDestruct "State" as (owner) "(>OA & >Sets & _)". + iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. + injection Heq as ->. + iDestruct (MutexSets.my_mutexes_exclusive with "[$Sets $Names]") as %[]. + rewrite /mutex_inv_namespace. + have Hnonempty := nclose_non_empty g.(state_namespace). + set_solver. + - iDestruct "State" as (owner) "(_ & >OF0 & _)". + iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. + Qed. + #[global] Instance locked_learn : Cbn (Learn (req_eq ==> learn_eq ==> req_eq ==> req_eq ==> learn_hints.fin) locked). - Proof. solve_learnable. Qed. + Proof using MUTEX_SETS. solve_learnable. Qed. Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ thr qt, current_thread thr ** not_locked this lk.1 thr qt ** @@ -312,15 +314,16 @@ Module CustomMutexPreds <: MUTEX_PREDS. Lemma init_R (this : ptr) (old : gname) (N : namespace) (P : mpred) : WeaklyObjective P -> + N <> nroot -> this |-> R old 1$m emp ** token old 1$m ** ▷P |-- (|={⊤}=> ∃ g, [| mutex_inv_namespace g = N |] ** this |-> R g 1$m P ** token g 1$m). Proof. - intros HP. iIntros "(HR & T & P)". + intros HP HN. iIntros "(HR & T & P)". iEval (rewrite /R _at_sep _at_as_Rep) in "HR". iDestruct "HR" as "(S & #CI & CO)". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. - iMod (alloc_state N) as (gs) "(%Hnamespace & Tnew & Stnew)". + iMod (alloc_state N HN) as (gs) "(%Hnamespace & Tnew & Stnew)". iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. - iModIntro. iExists (MkGname gs gi). iSplit; first (iPureIntro; exact Hnamespace). @@ -593,10 +596,11 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_id_specs.thread_idR _ _] "Owner". - iMod (alloc_state (nroot .@@ "MyMutex" .@ "inv_namespace")) as (gs) "(_ & T & State)". + iMod (alloc_state N ltac:(done)) as (gs) "(%Hnamespace & T & State)". iMod (cinv_alloc with "[L P Owner State]") as (gi) "[#CI CO]"; last first. - iModIntro. iExists (MkGname gs gi). + iSplit; first (iPureIntro; exact Hnamespace). rewrite /IR _at_sep _at_as_Rep /mutex_inv /=. iFrame "CI". iFrame. - iNext. iExists false. iFrame. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index f1461a9c..c49d4f3b 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -23,9 +23,7 @@ Module CustomRecursiveMutexName <: MutexCPPName. Definition cpp_ty : type := "MyRecursiveMutex"%cpp_type. End CustomRecursiveMutexName. -(* A custom recursive mutex implementation built on top of a mutex. - The definition for [token] and [not_locked] coincide with the ones in the - MutexPreds module. *) +(* A custom recursive mutex implementation built on top of a mutex. *) Module CustomRecursiveMutexPreds <: RECURSIVE_MUTEX_PREDS CustomRecursiveMutexName. Include RecursiveMutexState. @@ -37,6 +35,10 @@ Module CustomRecursiveMutexPreds inner_gname : mutex.gname; owner_gname : iprop.gname; cinv_gname : iprop.gname; + (* the atomic int field associates another invariant with + namespace [owner_namespace]. *) + owner_namespace : namespace; + owner_namespace_disjoint : mutex.mutex_inv_namespace inner_gname ## owner_namespace; }. Definition gname : Set := mutex_gname. Definition rmutex_inv_namespace (g : gname) : namespace := @@ -181,7 +183,7 @@ Module CustomRecursiveMutexPreds as_Rep (fun this => this ,, _field "MyRecursiveMutex::m_lock" |-> mutex.R g.(inner_gname) q (protected this g.(owner_gname) P) ** - cinv (rmutex_inv_namespace g) g.(cinv_gname) (I this g) ** + cinv g.(owner_namespace) g.(cinv_gname) (I this g) ** cinv_own g.(cinv_gname) q). #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(type_ptr,cfractional,ascfractional,cfracvalid)] derive R. @@ -256,12 +258,10 @@ Module custom_recursive_mutex. Context `{!G Σ}. Context `{MOD : source ⊧ σ}. - (** The wrapper adds its global sentinel and the count's objectivity - requirement to the shared recursive-mutex construction contract. *) + (** Recursive mutex specs with globals. *) cpp.spec "MyRecursiveMutex::MyRecursiveMutex()" as ctor_spec with ( \this this \prepost{qg} globals qg - \require WeaklyObjective (countR this 0) \exact Reduce (Spec.ctor_spec this)). cpp.spec "MyRecursiveMutex::~MyRecursiveMutex()" as dtor_spec with @@ -306,7 +306,11 @@ Module custom_recursive_mutex. \post not_locked container g qt th). (* The proof bodies in the rest of the section are generated by LLM. *) - Lemma ctor_proof : verify[source] ctor_spec. + (** The heap API leaves weak objectivity of the internal count as an + implementation hypothesis. *) + Lemma ctor_proof + `{count_objective : !forall this, WeaklyObjective (countR this 0)} : + verify[source] ctor_spec. Proof using MOD. (* Initialize the physical members first, with an empty inner invariant. *) verify_spec. @@ -314,29 +318,30 @@ Module custom_recursive_mutex. ghost state after the member constructors return. *) match goal with |- context[wp ?tu ?rho ?s ?K] => rewrite (lock (wp tu rho s K)) end. - go. iExists emp. go. + go. iExists N, emp. go. rewrite -lock. iApply fupd_wp. iDestruct select (|={⊤}=> _)%I as "Init". - iMod "Init" as (g) "[Inner Token]". + iMod "Init" as (g) "(_ & Inner & Token)". (* Install the zero count, empty owner fragment, and client resource in the inner mutex; keep the matching authority with the atomic owner. *) iMod (owner_alloc None) as (go) "[Auth Frag]". wname [_ |-> ulonglongR _ _] "Count". iDestruct select (@tele_app TT (fun _ => mpred) P xs) as "P". iMod (mutex.init_R (this ,, _field "MyRecursiveMutex::m_lock") - g N (protected this go (∃ xs, @tele_app TT (fun _ => mpred) P xs)) + g N (protected this go (∃ xs, @tele_app TT (fun _ => mpred) P xs)) ltac:(apply _) ltac:(done) with "[Inner Token Count Frag P]") as (inner) "(%Hnames & Inner & Token)". { iFrame "Inner Token". iNext. rewrite /protected /countR. iFrame. } wname [_ |-> atomic.R _ _ _] "Owner". - iMod (cinv_alloc ⊤ (mutex.mutex_inv_namespace inner) with "[Owner Auth]") as (gi) "[#CI CO]"; last first. - - iModIntro. go. iExists (MkGname inner go gi). + have [No Hdisjoint] := mutex.Spec.mutex_inv_namespace_disjoint inner. + iMod (cinv_alloc ⊤ No with "[Owner Auth]") as (gi) "[#CI CO]"; last first. + - iModIntro. go. iExists (MkGname inner go gi No Hdisjoint). iSplit; first (iPureIntro; reflexivity). rewrite /token /R _at_sep _at_as_Rep /I /=. iFrame "CI". iFrame. - iNext. rewrite /I /=. iExists None. iFrame. - - change (WeaklyObjective (I this (MkGname inner go 1%positive))). + - change (WeaklyObjective (I this (MkGname inner go 1%positive No Hdisjoint))). apply _. Qed. @@ -402,14 +407,15 @@ Module custom_recursive_mutex. iDestruct "HR" as "(S & Base & #CI & CO)". rewrite /std.atomic.do_load. iAcIntro. rewrite /commit_acc. - iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". - iAssert [| option_map fst owner <> Some th |] as %Hne. - { destruct owner as [[owner owner_qt]|]; last (iPureIntro; simpl; congruence). - destruct (decide (owner = th)) as [->|Hne]; last (iPureIntro; simpl; congruence). + destruct (decide (option_map fst owner = Some th)) as [Heq|Hne]. + { destruct owner as [[owner owner_qt]|]; simpl in Heq; last discriminate. + injection Heq as ->. iEval (rewrite /not_locked) in "Hhandle". - iDestruct (mutex.Spec.locked_not_locked_exclusive with "[$Handle $Hhandle]") as %[]. } + iMod (mutex.Spec.locked_not_locked_exclusive with "[$Base $Handle $Hhandle]") as %[]. + have Hdisjoint := owner_namespace_disjoint g. solve_ndisj. } iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. iExists (thread_id_specs.thread_id_hash (owner_id owner)), (1$m)%cQp. iSplitL "Owner"; first by ework $usenamed=true with br_erefl. @@ -440,7 +446,7 @@ Module custom_recursive_mutex. iDestruct "HR" as "(S & Base & #CI & CO)". rewrite /std.atomic.do_load. iAcIntro. rewrite /commit_acc. - iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". iEval (rewrite /owner_frag) in "Hfrag". @@ -467,7 +473,7 @@ Module custom_recursive_mutex. iDestruct "HR" as "(S & Base & #CI & CO)". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc. - iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". iEval (rewrite /owner_frag) in "Hfrag". @@ -511,7 +517,7 @@ Module custom_recursive_mutex. iDestruct "HR" as "(S & Base & #CI & CO)". rewrite /std.atomic.do_store. iAcIntro. rewrite /commit_acc. - iInv (rmutex_inv_namespace g) as "[>Inv CO]" "Hclose". + iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". iEval (rewrite /owner_frag) in "Hfrag". @@ -636,7 +642,8 @@ Module custom_recursive_mutex. thread_id_specs.hash_bytes_spec ** std.cassert.assert_fail_spec. - Lemma link : + Lemma link + `{count_objective : !forall this, WeaklyObjective (countR this 0)} : denoteModule source ** standard_library_specs |-- ctor_spec ** dtor_spec ** lock_spec ** unlock_spec. Proof using MOD. diff --git a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v index bab3d70e..e03e90c7 100644 --- a/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/double_incr_cpp_proof.v @@ -155,7 +155,7 @@ Section with_cpp. (* Logical invariant setup for the already initialized mutex. *) iMod (mutex.init_R (_global "m") γold (mutex.mutex_inv_namespace γold) (double_incr_protocol.protected γ (_global "x")) ltac:(apply _) - with "[Hm Htoken HP]") as (g) "(%Hnames & Hm & Htoken)". + (mutex.mutex_inv_namespace_not_root γold) with "[Hm Htoken HP]") as (g) "(%Hnames & Hm & Htoken)". { iFrame "Hm Htoken". iNext. done. } iDestruct "Hm" as "[Hm1 Hm2]". iEval (rewrite (cfractional_split_half (mutex.token g)) diff --git a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v index 803f3f26..680fe2cc 100644 --- a/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/guard_recursive_cpp_proof.v @@ -126,6 +126,7 @@ Section with_cpp. cpp.spec "C::C()" from source as C_ctor_spec with ( \this this \pre{N} emp + \require N <> nroot \post Exists γ, [| std_recursive_mutex.rmutex_inv_namespace γ = N |] ** this |-> CR γ 1$m ** std_recursive_mutex.token γ 1$m @@ -168,6 +169,8 @@ Section with_cpp. Lemma test_one_answer_ok : verify[source] "test_one_answer()". Proof. + have Hnamespace : nroot .@@ "guard_recursive" <> nroot. + { rewrite namespaces.ndot_unseal. discriminate. } verify_spec; go. iExists (nroot .@@ "guard_recursive"); go. iDestruct (MutexSets.my_mutexes_alloc_mutex_name diff --git a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v index 07b7b41c..9c87c9be 100644 --- a/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/transfer_with_mutex_cpp_proof.v @@ -371,6 +371,7 @@ Section class_C. \arg{initial_balance} "initial_balance" (Vint initial_balance) \persist{th} current_thread th \pre{N} emp + \require N <> nroot \post this |-> accountR N 1$m ). @@ -553,6 +554,10 @@ Section clients. Lemma main_ok : verify[source] main_spec. Proof. + have Hfrom : from_namespace <> nroot. + { rewrite /from_namespace namespaces.ndot_unseal. discriminate. } + have Hto : to_namespace <> nroot. + { rewrite /to_namespace namespaces.ndot_unseal. discriminate. } verify_spec. let rec expose_names := first [iDestruct select (MutexSets.my_mutexes th _) as "Hnames" From 66a7d9b89925af3941cec38d0c9d38323df24b5e Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 05:46:07 +0000 Subject: [PATCH 095/109] [custom_mutex_hpp_proof.v] and [custom_recursive_mutex_hpp_proof.v] now use the same ghost state to track the owner thread and how much [token] it deposits --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 47 +----- .../test/mutex/custom_mutex_hpp_proof.v | 151 ++++++++---------- .../mutex/custom_recursive_mutex_hpp_proof.v | 74 +-------- 3 files changed, 74 insertions(+), 198 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index 9751a7de..b202a902 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -58,41 +58,6 @@ Module Type MUTEX_TOKENS. ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. End MUTEX_TOKENS. -(** Authoritative and exclusive fragment ownership of an optional thread ID. *) -Module Type OWNER_TID. - Parameter cmraR : cmra. - - Class G `{Σ : cpp_logic} := { - #[local] has_own :: HasOwn (iPropI _Σ) cmraR; - #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; - #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; - }. - #[global] Arguments G {_ _} Σ : assert. - - Parameter owner_tid_auth owner_tid_frag : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> option thread_idT -> mpred. - - #[global] Declare Instance owner_tid_auth_timeless - `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_auth γ o). - #[global] Declare Instance owner_tid_frag_timeless - `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_frag γ o). - #[global] Declare Instance owner_tid_frag_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). - #[global] Declare Instance owner_tid_auth_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_auth γ o). - #[global] Declare Instance owner_tid_frag_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_frag γ o). - #[global] Declare Instance owner_agree - `{Σ : cpp_logic, !G Σ} γ o1 o2 : - Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). - - Axiom alloc : forall `{Σ : cpp_logic, !G Σ} o, - ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. - Axiom owner_update : forall `{Σ : cpp_logic, !G Σ} γ oa ofrag o', - owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- - (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). -End OWNER_TID. - Module mutex_set_G. Canonical Structure cmraR : cmra := thread_idT -d> coPset_disjR. @@ -484,9 +449,11 @@ Module MutexTokens <: MUTEX_TOKENS. End theory. End MutexTokens. -(** The exclusive-authoritative implementation of [OWNER_TID]. *) -Module OwnerTid : OWNER_TID. - Canonical Structure cmraR : cmra := excl_authR (optionO thread_idTO). +(** Authoritative and exclusive fragment ownership of an optional thread ID + and the exact token share deposited by that thread. *) +Module OwnerTid. + Canonical Structure cmraR : cmra := + excl_authR (optionO (prodO thread_idTO (leibnizO cQp.t))). Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; @@ -496,10 +463,10 @@ Module OwnerTid : OWNER_TID. #[global] Arguments G {_ _} Σ : assert. Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (o : option thread_idT) : mpred := + (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := own γ ((●E o) : cmraR). Definition owner_tid_frag `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (o : option thread_idT) : mpred := + (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := own γ ((◯E o) : cmraR). #[global] Instance owner_tid_auth_timeless diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 8a0cac74..f81b579d 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -1,6 +1,7 @@ (** Provisional *) Require Import iris.algebra.coPset. +Require Import iris.algebra.frac. Require Import skylabs.auto.cpp.proof. Require Import skylabs.auto.cpp.hints.base_derived. @@ -13,13 +14,14 @@ Require Import skylabs.brick.libstdcpp.thread.spec. NES.Open std.atomic. Import linearity. +Import OwnerTid. Require Import skylabs.brick.libstdcpp.test.mutex.custom_mutex_hpp. (** The concrete mutex supplies both the ghost state and the physical predicates used by [mutex_spec]. The mutex-set pool is shared, and the memory-order global remains a separate precondition of the C++ methods. *) Module CustomMutexPreds <: MUTEX_PREDS. - Import MutexSets MutexTokens OwnerTid. + Import MutexSets. Record state_gname : Set := MkStateGname { token_gname : iprop.gname; @@ -42,8 +44,12 @@ Module CustomMutexPreds <: MUTEX_PREDS. Lemma mutex_inv_namespace_not_root g : mutex_inv_namespace g <> nroot. Proof. exact g.(state_namespace_not_root). Qed. + Canonical Structure token_cmraR : cmra := fracR. + Class stateG `{Σ : cpp_logic} := { - #[global] tokens_G :: MutexTokens.G Σ; + #[local] has_token_own :: HasOwn (iPropI _Σ) token_cmraR; + #[local] has_token_upd :: HasOwnUpd (iPropI _Σ) token_cmraR; + #[local] has_token_valid :: HasOwnValid (iPropI _Σ) token_cmraR; #[global] owners_G :: OwnerTid.G Σ; }. Definition G := @stateG. @@ -51,56 +57,25 @@ Module CustomMutexPreds <: MUTEX_PREDS. #[global] Arguments G {_ _} Σ : assert. #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. - Definition state_token `{Σ : cpp_logic, !G Σ} - (γ : state_gname) (q : cQp.t) : mpred := - MutexTokens.token γ.(token_gname) q. - Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := - state_token γ q. + own γ.(token_gname) (cQp.frac q : token_cmraR). Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := token γ q ** MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace γ). - Definition owner_token `{Σ : cpp_logic, !G Σ} - (γ : state_gname) (th : thread_idT) (q : Qp) : mpred := - MutexTokens.given_token γ.(token_gname) q ** - OwnerTid.owner_tid_frag γ.(owner_gname) (Some th). - - #[global] Instance state_token_fractional - `{Σ : cpp_logic, !G Σ} γ : CFractional (state_token γ). - Proof. - intros q1 q2. rewrite /state_token cQp.frac_add. - apply MutexTokens.token_fractional. - Qed. - - #[global] Instance state_token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (state_token γ q). - Proof. rewrite /state_token. apply _. Qed. - - #[global] Instance owner_token_timeless - `{Σ : cpp_logic, !G Σ} γ th q : Timeless (owner_token γ th q). - Proof. rewrite /owner_token. apply _. Qed. - - #[global] Instance owner_token_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive2 (owner_token γ). - Proof. - intros th1 th2 q1 q2. rewrite /owner_token. - iIntros "[_ F1] [_ F2]". - iDestruct (OwnerTid.owner_tid_frag_exclusive with "F1 F2") as %[]. - Qed. - Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (γ : state_gname) (b : bool) : mpred := (if b then - ∃ th, OwnerTid.owner_tid_auth γ.(owner_gname) (Some th) ** - MutexSets.my_mutexes th - (coPset.CoPset $ ↑γ.(state_namespace)) ** - MutexTokens.token_not_full γ.(token_gname) + ∃ th q, owner_tid_auth γ.(owner_gname) (Some (th, q)) ** + (* the mutex owner deposits its [my_mutexes] and [token], and take away + owner_tid_frag which records the fraction of [token] that it can take + back from invariant when it releases the lock. *) + MutexSets.my_mutexes th (coPset.CoPset $ ↑γ.(state_namespace)) ** + own γ.(token_gname) (cQp.frac q : token_cmraR) else - ∃ owner, OwnerTid.owner_tid_auth γ.(owner_gname) owner ** - OwnerTid.owner_tid_frag γ.(owner_gname) owner ** - MutexTokens.token_full γ.(token_gname))%I. + owner_tid_auth γ.(owner_gname) None ** + owner_tid_frag γ.(owner_gname) None)%I. #[global] Instance state_WeaklyObjective `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ b : @@ -114,61 +89,58 @@ Module CustomMutexPreds <: MUTEX_PREDS. Lemma alloc_state (N : namespace) (HN : N <> nroot) : ⊢ |==> ∃ γ, [| γ.(state_namespace) = N |] ** - state_token γ 1$m ** state γ false. + own γ.(token_gname) (1%Qp : token_cmraR) ** state γ false. Proof. - iMod MutexTokens.alloc as (gt) "[T GT]". + iMod (own_alloc (1%Qp : token_cmraR)) as (gt) "T"; first done. iMod (OwnerTid.alloc None) as (go) "[OA OF]". iModIntro. iExists (MkStateGname gt go N HN). iSplit; first done. - rewrite /state_token /state /=. iFrame "T". iExists None. - iFrame "OA OF". - iApply MutexTokens.token_full_init. iExact "GT". + rewrite /state /=. iFrame. Qed. Lemma state_lock {σ : genv} this (γ : gname) th (q : cQp.t) : state γ false ** not_locked this γ th q |-- - (|==> state γ true ** owner_token γ th q). + (|==> state γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q))). Proof. - rewrite /state /not_locked /token /state_token /mutex_inv_namespace /owner_token. - iIntros "[State [T Sets]]". - iDestruct "State" as (owner) "(OA & OF & Balance)". - iDestruct (MutexTokens.acquire with "[$Balance $T]") as "[GT Balance]". - iMod (OwnerTid.owner_update γ.(owner_gname) _ _ (Some th) + rewrite /state /not_locked /token /mutex_inv_namespace. + iIntros "[[OA OF] [T Sets]]". + iMod (owner_update γ.(owner_gname) None None (Some (th, q)) with "[$OA $OF]") as "[OA OF]". - iModIntro. iFrame "GT OF". iExists th. iFrame. + iModIntro. iFrame "OF". iExists th, q. iFrame. Qed. Lemma state_unlock {σ : genv} this (γ : gname) th (q : cQp.t) : - state γ true ** owner_token γ th q |-- + state γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- (|==> state γ false ** not_locked this γ th q). Proof. - rewrite /state /owner_token /not_locked /token /state_token /mutex_inv_namespace. - iIntros "[State [GT OF]]". - iDestruct "State" as (owner) "(OA & Sets & Balance)". - iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. - injection Heq as ->. - iDestruct (MutexTokens.release with "[$Balance $GT]") as "[T Balance]". - iModIntro. iFrame "Sets T". iExists (Some th). iFrame. + rewrite /state /not_locked /token /mutex_inv_namespace. + iIntros "[State OF]". + iDestruct "State" as (owner held_q) "(OA & Sets & T)". + iDestruct (observe_2 [| Some (owner, held_q) = Some (th, q) |] + with "OA OF") as %Heq. + injection Heq as -> ->. + iMod (owner_update γ.(owner_gname) _ _ None with "[$OA $OF]") as "[OA OF]". + iModIntro. iFrame. Qed. - Lemma unlocked_owner_token γ th q : - state γ false ** owner_token γ th q |-- False. + Lemma unlocked_owner_tid_frag γ th q : + state γ false ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- False. Proof. - rewrite /state /owner_token. iIntros "[State [_ OF]]". - iDestruct "State" as (owner) "(_ & OF0 & _)". - iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. + rewrite /state. iIntros "[[_ OF0] OF]". + iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. Lemma locked_full_token γ : - state γ true ** state_token γ 1$m |-- False. + state γ true ** own γ.(token_gname) (1%Qp : token_cmraR) |-- False. Proof. - rewrite /state /state_token. iIntros "[State T]". - iDestruct "State" as (th) "(_ & _ & Balance)". - iApply (MutexTokens.token_not_full_full_token with "[$Balance $T]"). + rewrite /state. iIntros "[State T]". + iDestruct "State" as (th q) "(_ & _ & Held)". + iDestruct (own_valid_2 with "T Held") as %Hbad. + iPureIntro. exact (Qp.not_add_le_l 1 (cQp.frac q) Hbad). Qed. End state_laws. - #[global] Hint Opaque state_token not_locked owner_token state : sl_opacity typeclass_instances. + #[global] Hint Opaque not_locked state : sl_opacity typeclass_instances. Definition globals `{Σ : cpp_logic} {σ : genv} (q : cQp.t) : mpred := _global "std::memory_order_seq_cst" |-> @@ -180,14 +152,16 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m th ** - owner_token γ th q. + owner_tid_frag γ.(owner_gname) (Some (th, q)). #[global] Hint Opaque token : sl_opacity typeclass_instances. #[global] Hint Opaque locked : typeclass_instances. #[global] Arguments locked /. #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : CFractional (token γ). - Proof. rewrite /token. apply state_token_fractional. Qed. + Proof. + intros q1 q2. rewrite /token cQp.frac_add -own_op. done. + Qed. #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). Proof. rewrite /token. apply _. Qed. @@ -219,7 +193,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. #[global] Instance locked_WeaklyObjective `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ th q : WeaklyObjective (locked this γ th q). - Proof. rewrite /locked /owner_token /MutexTokens.given_token. apply _. Qed. + Proof. rewrite /locked. apply _. Qed. Section with_Σ. Context `{Σ : cpp_logic}. @@ -263,19 +237,20 @@ Module CustomMutexPreds <: MUTEX_PREDS. iInv (mutex_inv_namespace g) as "[Inv CO]" "Hclose". iEval (rewrite /mutex_inv) in "Inv". iDestruct "Inv" as (b) "(_ & State & _)". - iEval (rewrite /locked /owner_token) in "Locked". - iDestruct "Locked" as "(_ & _ & OF)". + iEval (rewrite /locked) in "Locked". + iDestruct "Locked" as "[_ OF]". iEval (rewrite /state) in "State". destruct b. - - iDestruct "State" as (owner) "(>OA & >Sets & _)". - iDestruct (observe_2 [| Some owner = Some th |] with "OA OF") as %Heq. - injection Heq as ->. + - iDestruct "State" as (owner held_q) "(>OA & >Sets & _)". + iDestruct (observe_2 [| Some (owner, held_q) = Some (th, qt) |] + with "OA OF") as %Heq. + injection Heq as -> ->. iDestruct (MutexSets.my_mutexes_exclusive with "[$Sets $Names]") as %[]. rewrite /mutex_inv_namespace. have Hnonempty := nclose_non_empty g.(state_namespace). set_solver. - - iDestruct "State" as (owner) "(_ & >OF0 & _)". - iDestruct (OwnerTid.owner_tid_frag_exclusive with "OF0 OF") as %[]. + - iDestruct "State" as "[_ >OF0]". + iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. #[global] Instance locked_learn : @@ -380,7 +355,7 @@ Module custom_mutex. \pre{qt} not_locked this g thr qt \post P ** this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** - owner_token g thr qt). + owner_tid_frag g.(owner_gname) (Some (thr, qt))). cpp.spec "MyMutex::do_unlock()" as do_unlock_spec with ( \this this @@ -388,7 +363,7 @@ Module custom_mutex. \persist{thr} current_thread thr \prepost{qg} globals qg \pre{(qt : cQp.t)} this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id ** - owner_token g thr qt + owner_tid_frag g.(owner_gname) (Some (thr, qt)) \pre ▷P \post not_locked this g thr qt). @@ -448,7 +423,7 @@ Module custom_mutex. \proving{K (_ : IsExistential K)} std.atomic.do_exchange "int" (BASE (p,, o_field σ "MyMutex::m_lock") ) 1%Z K \instantiate K := (fun res => p |-> IR g q P ** [| res = 0 \/ res = 1 |]%Z ** - if bool_decide (res = 0) then P ** owner_token g thr qt ** + if bool_decide (res = 0) then P ** owner_tid_frag g.(owner_gname) (Some (thr, qt)) ** p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id else not_locked p g thr qt) \end@{mpredI}. @@ -502,7 +477,7 @@ Module custom_mutex. \consuming{g q P} p |-> IR g q P \consuming P \consuming p ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id - \consuming{(qt : cQp.t)} owner_token g thr qt + \consuming{(qt : cQp.t)} owner_tid_frag g.(owner_gname) (Some (thr, qt)) \proving{K (_ : IsExistential K)} std.atomic.do_store "int" (BASE (p ,, o_field σ "MyMutex::m_lock")) 0%Z K \instantiate K := (p |-> IR g q P ** not_locked p g thr qt) @@ -532,7 +507,7 @@ Module custom_mutex. iModIntro. rewrite /IR _at_sep _at_as_Rep /=. iFrame "CI". iFrame. - - iDestruct (unlocked_owner_token with "[$State $Locked]") as %[]. + - iDestruct (unlocked_owner_tid_frag with "[$State $Locked]") as %[]. Qed. Hint Resolve do_store_C : sl_opacity. @@ -584,7 +559,7 @@ Module custom_mutex. Proof using MOD HAS_THREADS. verify_spec. repeat (go; ework). - wname [CustomMutexPreds.owner_token] "Owner". + wname [owner_tid_frag] "Owner". iFrame "Owner". ego. Unshelve. all: exact (1$m)%cQp. Qed. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index c49d4f3b..7d3d763c 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -11,6 +11,7 @@ Require Import skylabs.auto.cpp.prelude.proof. Require Import skylabs.brick.libstdcpp.atomic.spec. Require Import skylabs.brick.libstdcpp.cassert.spec. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. Require Import skylabs.brick.libstdcpp.thread.spec. Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. @@ -18,6 +19,7 @@ Require Import skylabs.brick.libstdcpp.test.mutex.custom_recursive_mutex_hpp. NES.Open std.atomic. Import linearity. +Import OwnerTid. Module CustomRecursiveMutexName <: MutexCPPName. Definition cpp_ty : type := "MyRecursiveMutex"%cpp_type. @@ -44,83 +46,15 @@ Module CustomRecursiveMutexPreds Definition rmutex_inv_namespace (g : gname) : namespace := mutex.mutex_inv_namespace g.(inner_gname). - (** If there is [Some] owner, it records the thread ID [th] and the fraction - in the lock handle [acquireable] for thread [th]. *) - Canonical Structure owner_cmraR : cmra := - excl_authR (optionO (prodO thread_idTO (leibnizO cQp.t))). - Class stateG `{Σ : cpp_logic} := { #[global] mutex_G :: mutex.G Σ; - #[local] has_owner_own :: HasOwn (iPropI _Σ) owner_cmraR; - #[local] has_owner_upd :: HasOwnUpd (iPropI _Σ) owner_cmraR; - #[local] has_owner_valid :: HasOwnValid (iPropI _Σ) owner_cmraR; + #[global] owners_G :: OwnerTid.G Σ; }. Definition G := @stateG. Existing Class G. #[global] Arguments G {_ _} Σ : assert. #[global] Instance state_G `{Σ : cpp_logic} (H : G Σ) : @stateG _ _ Σ := H. - - Definition owner_tid_auth `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := - own γ ((●E o) : owner_cmraR). - Definition owner_tid_frag `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (o : option (thread_idT * cQp.t)) : mpred := - own γ ((◯E o) : owner_cmraR). - - #[global] Instance owner_tid_auth_timeless - `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_auth γ o). - Proof. rewrite /owner_tid_auth. apply _. Qed. - #[global] Instance owner_tid_frag_timeless - `{Σ : cpp_logic, !G Σ} γ o : Timeless (owner_tid_frag γ o). - Proof. rewrite /owner_tid_frag. apply _. Qed. - - #[global] Instance owner_tid_frag_exclusive - `{Σ : cpp_logic, !G Σ} γ : Exclusive1 (owner_tid_frag γ). - Proof. - intros o1 o2. rewrite /owner_tid_frag. - iIntros "H1 H2". - iDestruct (own_valid_2 with "H1 H2") as %Hvalid. - move: Hvalid. rewrite excl_auth_frag_op_valid. done. - Qed. - - #[global] Instance owner_tid_auth_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_auth γ o). - Proof. rewrite /owner_tid_auth. apply _. Qed. - #[global] Instance owner_tid_frag_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ o : WeaklyObjective (owner_tid_frag γ o). - Proof. rewrite /owner_tid_frag. apply _. Qed. - - #[global] Instance owner_agree `{Σ : cpp_logic, !G Σ} γ o1 o2 : - Observe2 [| o1 = o2 |] (owner_tid_auth γ o1) (owner_tid_frag γ o2). - Proof. - apply observe_2_intro_only_provable. - rewrite /owner_tid_auth /owner_tid_frag. iIntros "A F". - iDestruct (own_valid_2 with "A F") as %HV. - iPureIntro. apply leibniz_equiv, excl_auth_agree, HV. - Qed. - - Lemma owner_alloc `{Σ : cpp_logic, !G Σ} o : - ⊢ |==> ∃ γ, owner_tid_auth γ o ** owner_tid_frag γ o. - Proof. - iMod (own_alloc ((●E o ⋅ ◯E o) : owner_cmraR)) as (γ) "H". - { apply excl_auth_valid. } - iModIntro. iExists γ. - rewrite /owner_tid_auth /owner_tid_frag -own_op. iExact "H". - Qed. - - Lemma owner_update `{Σ : cpp_logic, !G Σ} γ oa ofrag o' : - owner_tid_auth γ oa ** owner_tid_frag γ ofrag |-- - (|==> owner_tid_auth γ o' ** owner_tid_frag γ o'). - Proof. - rewrite /owner_tid_auth /owner_tid_frag. iIntros "[A F]". - iMod (own_update_2 with "A F") as "[$ $]"; - first apply (excl_auth_update _ _ o'). - done. - Qed. - - #[global] Hint Opaque owner_tid_auth owner_tid_frag : sl_opacity typeclass_instances. - Definition token `{Σ : cpp_logic, !G Σ} (g : gname) (q : cQp.t) : mpred := mutex.token g.(inner_gname) q. #[global] Hint Opaque token : sl_opacity typeclass_instances. @@ -325,7 +259,7 @@ Module custom_recursive_mutex. iMod "Init" as (g) "(_ & Inner & Token)". (* Install the zero count, empty owner fragment, and client resource in the inner mutex; keep the matching authority with the atomic owner. *) - iMod (owner_alloc None) as (go) "[Auth Frag]". + iMod (OwnerTid.alloc None) as (go) "[Auth Frag]". wname [_ |-> ulonglongR _ _] "Count". iDestruct select (@tele_app TT (fun _ => mpred) P xs) as "P". iMod (mutex.init_R (this ,, _field "MyRecursiveMutex::m_lock") From 82c4685415943564e95c6b536a3c1c35065e3f73 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 05:53:06 +0000 Subject: [PATCH 096/109] [custom_mutex_hpp_proof.v] lift owner_tid_auth out of the match in state --- .../test/mutex/custom_mutex_hpp_proof.v | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index f81b579d..fd94f9fd 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -66,16 +66,16 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} (γ : state_gname) (b : bool) : mpred := - (if b then - ∃ th q, owner_tid_auth γ.(owner_gname) (Some (th, q)) ** + ∃ th q, + owner_tid_auth γ.(owner_gname) (if b then Some (th, q) else None) ** + if b then (* the mutex owner deposits its [my_mutexes] and [token], and take away owner_tid_frag which records the fraction of [token] that it can take back from invariant when it releases the lock. *) MutexSets.my_mutexes th (coPset.CoPset $ ↑γ.(state_namespace)) ** own γ.(token_gname) (cQp.frac q : token_cmraR) - else - owner_tid_auth γ.(owner_gname) None ** - owner_tid_frag γ.(owner_gname) None)%I. + else + owner_tid_frag γ.(owner_gname) None. #[global] Instance state_WeaklyObjective `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ b : @@ -95,7 +95,8 @@ Module CustomMutexPreds <: MUTEX_PREDS. iMod (OwnerTid.alloc None) as (go) "[OA OF]". iModIntro. iExists (MkStateGname gt go N HN). iSplit; first done. - rewrite /state /=. iFrame. + rewrite /state /=. iFrame "T". + iExists thread_id_specs.default_thread_id, (1$m)%cQp. iFrame. Qed. Lemma state_lock {σ : genv} this (γ : gname) th (q : cQp.t) : @@ -103,7 +104,8 @@ Module CustomMutexPreds <: MUTEX_PREDS. (|==> state γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q))). Proof. rewrite /state /not_locked /token /mutex_inv_namespace. - iIntros "[[OA OF] [T Sets]]". + iIntros "[State [T Sets]]". + iDestruct "State" as (old_th old_q) "[OA OF]". iMod (owner_update γ.(owner_gname) None None (Some (th, q)) with "[$OA $OF]") as "[OA OF]". iModIntro. iFrame "OF". iExists th, q. iFrame. @@ -120,13 +122,14 @@ Module CustomMutexPreds <: MUTEX_PREDS. with "OA OF") as %Heq. injection Heq as -> ->. iMod (owner_update γ.(owner_gname) _ _ None with "[$OA $OF]") as "[OA OF]". - iModIntro. iFrame. + iModIntro. iFrame "Sets T". iExists th, q. iFrame. Qed. Lemma unlocked_owner_tid_frag γ th q : state γ false ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- False. Proof. - rewrite /state. iIntros "[[_ OF0] OF]". + rewrite /state. iIntros "[State OF]". + iDestruct "State" as (old_th old_q) "[_ OF0]". iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. @@ -249,7 +252,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. rewrite /mutex_inv_namespace. have Hnonempty := nclose_non_empty g.(state_namespace). set_solver. - - iDestruct "State" as "[_ >OF0]". + - iDestruct "State" as (owner held_q) "[_ >OF0]". iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. From b275f828b640194179f32a0670354a87439f441d Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 05:56:34 +0000 Subject: [PATCH 097/109] [custom_recursive_mutex_hpp_proof.v] inline an unnecessary definition --- .../mutex/custom_recursive_mutex_hpp_proof.v | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index 7d3d763c..033e4093 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -66,10 +66,6 @@ Module CustomRecursiveMutexPreds Timeless (token g qt). Proof. rewrite /token. apply _. Qed. - Definition owner_frag `{Σ : cpp_logic, !G Σ} - (g : gname) (owner : option (thread_idT * cQp.t)) : mpred := - owner_tid_frag g.(owner_gname) owner. - Definition countR `{Σ : cpp_logic, σ : genv} (this : ptr) (n : Z) : mpred := this ,, _field "MyRecursiveMutex::m_count" |-> ulonglongR 1$m n. @@ -134,7 +130,7 @@ Module CustomRecursiveMutexPreds Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} (this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := - countR this (Z.of_nat (S n)) ** owner_frag g (Some (th, qt)). + countR this (Z.of_nat (S n)) ** owner_tid_frag g.(owner_gname) (Some (th, qt)). #[global] Hint Opaque not_locked locked : sl_opacity typeclass_instances. @@ -145,9 +141,6 @@ Module CustomRecursiveMutexPreds intros [[th qt]|]; apply _. Qed. - #[global] Instance owner_frag_timeless - `{Σ : cpp_logic, !G Σ} g owner : Timeless (owner_frag g owner). - Proof. rewrite /owner_frag. apply _. Qed. #[global] Instance locked_timeless `{Σ : cpp_logic, !G Σ, σ : genv} this g qt th n : Timeless (locked this g qt th n). @@ -162,9 +155,6 @@ Module CustomRecursiveMutexPreds WeaklyObjective (p |-> R). Proof. rewrite INTERNAL._at_eq. apply _. Qed. - #[global] Instance owner_frag_WeaklyObjective - `{Σ : cpp_logic, !G Σ} g owner : WeaklyObjective (owner_frag g owner). - Proof. rewrite /owner_frag. apply _. Qed. #[global] Instance I_WeaklyObjective `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g : WeaklyObjective (I this g). @@ -304,9 +294,9 @@ Module custom_recursive_mutex. Qed. #[local] Existing Instance mpred_BiAffine. - #[local] Hint Opaque owner_frag locked not_locked + #[local] Hint Opaque locked not_locked : sl_opacity typeclass_instances. - #[local] Instance owner_frag_learn : LearnEq2 owner_frag. + #[local] Instance owner_tid_frag_learn : LearnEq2 owner_tid_frag. Proof. solve_learnable. Qed. Abbreviation BASE p := @@ -357,7 +347,7 @@ Module custom_recursive_mutex. iMod ("Hclose" with "[Owner Auth Handle]") as "_". { iNext. rewrite /I. iExists owner. iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } - iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + iModIntro. rewrite /R _at_sep _at_as_Rep. iFrame "#∗". iPureIntro. intros Heq. apply thread_id_specs.thread_id_hash_injective in Heq. destruct owner as [[owner owner_qt]|]; simpl in Heq, Hne; naive_solver. @@ -368,11 +358,11 @@ Module custom_recursive_mutex. Definition load_held_C (this : ptr) := \cancelx \consuming{g q P} this |-> R g q P - \consuming{th qt} owner_frag g (Some (th, qt)) + \consuming{th qt} owner_tid_frag g.(owner_gname) (Some (th, qt)) \proving{K (_ : IsExistential K)} std.atomic.do_load Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) K \instantiate K := (fun owner => this |-> R g q P ** - owner_frag g (Some (th, qt)) ** [| owner = thread_id_specs.thread_id_hash th |]) + owner_tid_frag g.(owner_gname) (Some (th, qt)) ** [| owner = thread_id_specs.thread_id_hash th |]) \end@{mpredI}. Next Obligation. intros. iIntros "[HR Hfrag]" (?? ->). @@ -383,7 +373,6 @@ Module custom_recursive_mutex. iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". - iEval (rewrite /owner_frag) in "Hfrag". iDestruct (observe_2 [| owner = Some (th, qt) |] with "Auth Hfrag") as %->. iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. iExists (thread_id_specs.thread_id_hash th), (1$m)%cQp. @@ -392,7 +381,7 @@ Module custom_recursive_mutex. iMod ("Hclose" with "[Owner Auth Handle]") as "_". { iNext. rewrite /I. iExists (Some (th, qt)). iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } - iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". done. + iModIntro. rewrite /R _at_sep _at_as_Rep. iFrame "#∗". done. Qed. #[local] Hint Resolve load_held_C : sl_opacity. @@ -400,7 +389,7 @@ Module custom_recursive_mutex. this |-> R g q P ** owner_tid_frag g.(owner_gname) None ** mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt |-- std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) - (thread_id_specs.thread_id_hash th) (this |-> R g q P ** owner_frag g (Some (th, qt))). + (thread_id_specs.thread_id_hash th) (this |-> R g q P ** owner_tid_frag g.(owner_gname) (Some (th, qt))). Proof using Type MOD. iIntros "(HR & Hfrag & Hhandle)". iEval (rewrite /R _at_sep _at_as_Rep) in "HR". @@ -410,7 +399,6 @@ Module custom_recursive_mutex. iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". - iEval (rewrite /owner_frag) in "Hfrag". iDestruct (observe_2 [| owner = None |] with "Auth Hfrag") as %->. iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. iExists (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id). @@ -421,7 +409,7 @@ Module custom_recursive_mutex. iMod ("Hclose" with "[Owner Auth Hhandle]") as "_". { iNext. rewrite /I. iExists (Some (th, qt)). iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } - iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + iModIntro. rewrite /R _at_sep _at_as_Rep. iFrame "#∗". Qed. #[program] @@ -433,7 +421,7 @@ Module custom_recursive_mutex. (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt \proving{K (_ : IsExistential K)} std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) (thread_id_specs.thread_id_hash th) K - \instantiate K := (this |-> R g q P ** owner_frag g (Some (th, qt))) + \instantiate K := (this |-> R g q P ** owner_tid_frag g.(owner_gname) (Some (th, qt))) \end@{mpredI}. Next Obligation. intros. iIntros "Hpre" (?? ->). iApply (store_owner_action with "Hpre"). @@ -441,9 +429,9 @@ Module custom_recursive_mutex. #[local] Hint Resolve store_owner_C : sl_opacity. Lemma clear_owner_action (this : ptr) g q P th qt : - this |-> R g q P ** owner_frag g (Some (th, qt)) |-- + this |-> R g q P ** owner_tid_frag g.(owner_gname) (Some (th, qt)) |-- std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) - (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id) (this |-> R g q P ** owner_frag g None ** + (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id) (this |-> R g q P ** owner_tid_frag g.(owner_gname) None ** mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt). Proof using Type MOD. iIntros "[HR Hfrag]". @@ -454,7 +442,6 @@ Module custom_recursive_mutex. iInv (owner_namespace g) as "[>Inv CO]" "Hclose". iEval (rewrite /I) in "Inv". iDestruct "Inv" as (owner) "(Owner & Auth & Handle)". - iEval (rewrite /owner_frag) in "Hfrag". iDestruct (observe_2 [| owner = Some (th, qt) |] with "Auth Hfrag") as %->. iDestruct (fupd_mask_subseteq) as ">Y"; [| iModIntro]; first set_solver. iExists (thread_id_specs.thread_id_hash th). @@ -465,17 +452,17 @@ Module custom_recursive_mutex. iMod ("Hclose" with "[Owner Auth]") as "_". { iNext. rewrite /I. iExists None. iSplitL "Owner"; first by ework $usenamed=true with br_erefl. iFrame. } - iModIntro. rewrite /R /owner_frag _at_sep _at_as_Rep. iFrame "#∗". + iModIntro. rewrite /R _at_sep _at_as_Rep. iFrame "#∗". Qed. #[program] Definition clear_owner_C (this : ptr) := \cancelx \consuming{g q P} this |-> R g q P - \consuming{th qt} owner_frag g (Some (th, qt)) + \consuming{th qt} owner_tid_frag g.(owner_gname) (Some (th, qt)) \proving{K (_ : IsExistential K)} std.atomic.do_store Tulong (BASE (this ,, _field "MyRecursiveMutex::m_owner")) (thread_id_specs.thread_id_hash thread_id_specs.default_thread_id) K - \instantiate K := (this |-> R g q P ** owner_frag g None ** + \instantiate K := (this |-> R g q P ** owner_tid_frag g.(owner_gname) None ** mutex.locked (this ,, _field "MyRecursiveMutex::m_lock") g.(inner_gname) th qt) \end@{mpredI}. Next Obligation. @@ -539,9 +526,9 @@ Module custom_recursive_mutex. wname [mutex.locked] "Locked". iFrame "Locked". wname [P] "P". wname [_ |-> ulonglongR _ _] "Count". - wname [owner_frag] "OF". + wname [owner_tid_frag] "OF". iSplitL "P Count OF". - { iNext. rewrite /protected /countR /owner_frag. + { iNext. rewrite /protected /countR. iFrame "Count OF". iExists args. iExact "P". } ego. rewrite Spec.acquireable.unlock release.unlock. iFrame "#∗". (* Nested release: decrement the count and keep the resource. *) From 38165279fbb6eebc4b8ad89fc8ea8ecaf3a0dffd Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 05:59:56 +0000 Subject: [PATCH 098/109] [lock_ghost2.v] remove the unnecessary MUTEX_TOKENS module type, keep the module --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 46 +------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index b202a902..b9cecbbc 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -14,50 +14,6 @@ Import linearity. (** Various ghost state constructions and laws for concurrency library specs and proofs. *) -Module Type MUTEX_TOKENS. - Parameter cmraR : cmra. - - Class G `{Σ : cpp_logic} := { - #[local] has_own :: HasOwn (iPropI _Σ) cmraR; - #[local] has_upd :: HasOwnUpd (iPropI _Σ) cmraR; - #[local] has_valid :: HasOwnValid (iPropI _Σ) cmraR; - }. - #[global] Arguments G {_ _} Σ : assert. - - Parameter token : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> Qp -> mpred. - Parameter given_token : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> Qp -> mpred. - - #[global] Declare Instance token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). - #[global] Declare Instance given_token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (given_token γ). - #[global] Declare Instance token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). - #[global] Declare Instance given_token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). - - Parameter token_full token_not_full : forall `{Σ : cpp_logic, !G Σ}, - iprop.gname -> mpred. - #[global] Declare Instance token_full_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ : WeaklyObjective (token_full γ). - #[global] Declare Instance token_not_full_WeaklyObjective - `{Σ : cpp_logic, !G Σ} γ : WeaklyObjective (token_not_full γ). - - Axiom token_full_init : forall `{Σ : cpp_logic, !G Σ} γ, - given_token γ 1 |-- token_full γ. - Axiom acquire : forall `{Σ : cpp_logic, !G Σ} γ q, - token_full γ ** token γ q |-- given_token γ q ** token_not_full γ. - Axiom release : forall `{Σ : cpp_logic, !G Σ} γ q, - token_not_full γ ** given_token γ q |-- token γ q ** token_full γ. - Axiom token_not_full_full_token : forall `{Σ : cpp_logic, !G Σ} γ, - token_not_full γ ** token γ 1 |-- False. - - Axiom alloc : forall `{Σ : cpp_logic, !G Σ}, - ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. -End MUTEX_TOKENS. - Module mutex_set_G. Canonical Structure cmraR : cmra := thread_idT -d> coPset_disjR. @@ -281,7 +237,7 @@ End MutexSets. (** ** The fractional token/given-token pair *) -Module MutexTokens <: MUTEX_TOKENS. +Module MutexTokens. Canonical Structure cmraR : cmra := prodUR (optionUR fracR) (optionUR fracR). From bb267d25141c5ce0079e2e146a0f3718fe32117c Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 06:33:59 +0000 Subject: [PATCH 099/109] simplify the token module: do not need given_token, token is just a fracR --- rocq-brick-libstdcpp/proof/lib/lock_ghost2.v | 134 ++---------------- .../proof/mutex/spec/recursive_mutex_atomic.v | 78 ++++------ 2 files changed, 39 insertions(+), 173 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v index b9cecbbc..4c6de200 100644 --- a/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v +++ b/rocq-brick-libstdcpp/proof/lib/lock_ghost2.v @@ -235,11 +235,10 @@ Module MutexSets. #[global] Hint Opaque mutex_set_map my_mutexes : sl_opacity typeclass_instances. End MutexSets. -(** ** The fractional token/given-token pair *) +(** Tracks a fraction. *) Module MutexTokens. - Canonical Structure cmraR : cmra := - prodUR (optionUR fracR) (optionUR fracR). + Canonical Structure cmraR : cmra := fracR. Class G `{Σ : cpp_logic} := { #[local] has_own :: HasOwn (iPropI _Σ) cmraR; @@ -250,11 +249,7 @@ Module MutexTokens. Definition token `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) (q : Qp) : mpred := - own γ (Some q, None). - - Definition given_token `{Σ : cpp_logic, !G Σ} - (γ : iprop.gname) (q : Qp) : mpred := - own γ (None, Some q). + own γ (q : cmraR). #[global] Instance token_fractional `{Σ : cpp_logic, !G Σ} γ : Fractional (token γ). @@ -262,145 +257,32 @@ Module MutexTokens. intros q1 q2. rewrite /token -own_op /=. done. Qed. - #[global] Instance given_token_fractional - `{Σ : cpp_logic, !G Σ} γ : Fractional (given_token γ). - Proof. - intros q1 q2. rewrite /given_token -own_op /=. done. - Qed. - #[global] Instance token_timeless `{Σ : cpp_logic, !G Σ} γ q : Timeless (token γ q). Proof. rewrite /token. apply _. Qed. - #[global] Instance given_token_timeless - `{Σ : cpp_logic, !G Σ} γ q : Timeless (given_token γ q). - Proof. rewrite /given_token. apply _. Qed. - - (** Ordinary and given-token shares in the invariant add up to one. - [token_not_full] contains a positive ordinary-token share, so it conflicts - with the full ordinary token required for destruction. *) - Definition token_not_full `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) : mpred := - token γ 1 ∨ ∃ qt qg : Qp, - [| (qt + qg = 1)%Qp |] ** token γ qt ** given_token γ qg. - - (** Include the endpoint with no ordinary tokens. Transfers preserve the - balance even when a caller returns only part of its given-token share. *) - Definition token_full `{Σ : cpp_logic, !G Σ} (γ : iprop.gname) : mpred := - given_token γ 1 ∨ token_not_full γ. - - #[global] Instance token_full_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ : - WeaklyObjective (token_full γ). - Proof. rewrite /token_full /token_not_full /token /given_token. apply _. Qed. - - #[global] Instance token_not_full_WeaklyObjective `{Σ : cpp_logic, !G Σ} γ : - WeaklyObjective (token_not_full γ). - Proof. rewrite /token_not_full /token /given_token. apply _. Qed. - - #[global] Hint Opaque token given_token token_not_full token_full : sl_opacity typeclass_instances. + #[global] Hint Opaque token : sl_opacity typeclass_instances. Section theory. Context `{Σ : cpp_logic, !G Σ}. #[local] Existing Instance mpred_BiAffine. - Lemma token_full_init γ : given_token γ 1 |-- token_full γ. - Proof. rewrite /token_full. iIntros "T". iLeft. iExact "T". Qed. - Lemma token_valid γ q : token γ q |-- [| (q ≤ 1)%Qp |]. Proof. rewrite /token. iIntros "H". iDestruct (own_valid with "H") as %Hvalid. - iPureIntro. exact (proj1 Hvalid). - Qed. - - Lemma given_token_valid γ q : given_token γ q |-- [| (q ≤ 1)%Qp |]. - Proof. - rewrite /given_token. iIntros "H". - iDestruct (own_valid with "H") as %Hvalid. - iPureIntro. exact (proj2 Hvalid). + iPureIntro. exact Hvalid. Qed. Lemma token_valid_2 γ q1 q2 : token γ q1 ** token γ q2 |-- [| (q1 + q2 ≤ 1)%Qp |]. Proof. rewrite -fractional. apply token_valid. Qed. - Lemma given_token_valid_2 γ q1 q2 : - given_token γ q1 ** given_token γ q2 |-- [| (q1 + q2 ≤ 1)%Qp |]. - Proof. rewrite -fractional. apply given_token_valid. Qed. - - Lemma token_not_full_full_token γ : - token_not_full γ ** token γ 1 |-- False. - Proof. - rewrite /token_not_full. iIntros "[H T]". - iDestruct "H" as "[H | H]". - - iDestruct (token_valid_2 with "[$T $H]") as %Hbad. - exfalso. exact (Qp.not_add_le_l 1 1 Hbad). - - iDestruct "H" as (qt qg) "(_ & H & _)". - iDestruct (token_valid_2 with "[$T $H]") as %Hbad. - exfalso. exact (Qp.not_add_le_l 1 qt Hbad). - Qed. - - Lemma acquire γ q : - token_full γ ** token γ q |-- given_token γ q ** token_not_full γ. - Proof. - rewrite /token_full /token_not_full. iIntros "[H T]". - iDestruct "H" as "[H | [H | H]]". - - iDestruct (token_valid with "T") as %Hq. - apply Qp.le_lteq in Hq as [Hq | ->]. - + apply Qp.lt_sum in Hq as [r Hr]. - iEval (rewrite Hr fractional) in "H". - iDestruct "H" as "[H R]". iFrame "H". - iRight. iExists q, r. iFrame. done. - + iFrame "H". iLeft. iExact "T". - - iDestruct (token_valid_2 with "[$H $T]") as %Hbad. - exfalso. exact (Qp.not_add_le_l 1 q Hbad). - - iDestruct "H" as (qt qg) "(%Hsum & T0 & G)". - iDestruct (token_valid_2 with "[$T0 $T]") as %Hvalid. - rewrite -Hsum in Hvalid. - apply Qp.add_le_mono_l, Qp.le_lteq in Hvalid as [Hlt | ->]. - + apply Qp.lt_sum in Hlt as [r Hr]. - iEval (rewrite Hr fractional) in "G". - iDestruct "G" as "[G R]". iFrame "G". - iRight. iExists (qt + q)%Qp, r. - iSplit; first (iPureIntro; by rewrite -Qp.add_assoc -Hr). - iFrame "R". rewrite fractional. iFrame. - + iFrame "G". iLeft. rewrite -Hsum fractional. iFrame. - Qed. - - Lemma release γ q : - token_not_full γ ** given_token γ q |-- token γ q ** token_full γ. - Proof. - rewrite /token_full /token_not_full. iIntros "[H G]". - iDestruct "H" as "[H | H]". - - iDestruct (given_token_valid with "G") as %Hq. - apply Qp.le_lteq in Hq as [Hq | ->]. - + apply Qp.lt_sum in Hq as [r Hr]. - iEval (rewrite Hr fractional) in "H". - iDestruct "H" as "[H R]". iFrame "H". - iRight. iRight. iExists r, q. iFrame. - iPureIntro. by rewrite Qp.add_comm. - + iFrame "H". iLeft. iExact "G". - - iDestruct "H" as (qt qg) "(%Hsum & T & G0)". - iDestruct (given_token_valid_2 with "[$G $G0]") as %Hvalid. - rewrite -Hsum in Hvalid. - apply Qp.add_le_mono_r, Qp.le_lteq in Hvalid as [Hlt | ->]. - + apply Qp.lt_sum in Hlt as [r Hr]. - iEval (rewrite Hr fractional) in "T". - iDestruct "T" as "[T R]". iFrame "T". - iRight. iRight. iExists r, (q + qg)%Qp. - iSplit; first (iPureIntro; by rewrite Qp.add_assoc (Qp.add_comm r q) -Hr). - iFrame "R". rewrite fractional. iFrame. - + iFrame "T". iLeft. rewrite -Hsum fractional. iFrame. - Qed. - - Lemma alloc : - ⊢ |==> ∃ γ, token γ 1 ** given_token γ 1. + Lemma alloc : ⊢ |==> ∃ γ, token γ 1. Proof. - iMod (own_alloc - (((Some 1%Qp, None) ⋅ (None, Some 1%Qp)) : cmraR)) as (γ) "H". - { done. } - iModIntro. iExists γ. - rewrite /token /given_token -own_op. iExact "H". + iMod (own_alloc (1%Qp : cmraR)) as (γ) "H"; first done. + iModIntro. iExists γ. iExact "H". Qed. End theory. End MutexTokens. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v index de7011ff..5012990b 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex_atomic.v @@ -30,9 +30,10 @@ Import linearity. Module RecursiveMutexAtomicSpecs (T : MutexCPPName). (* FIXME the hardcoded strings can be derived from T.cpp_ty. *) - (* Not prodO thread_idTO natO. *) (* [locked γ qt th 0] does not identify the thread holding the mutex. *) - Canonical Structure phys_stateUR := authR (optionR (exclR (prodO thread_idTO natO))). + (** The exclusive owner/count fragment also records the deposited token share. *) + Canonical Structure phys_stateUR := + authR (optionR (exclR (prodO (prodO thread_idTO natO) (leibnizO cQp.t)))). (** [locked γ qt th n] records [n] acquisitions by [th], using token share [qt]. *) Class lockedG `{Σ : cpp_logic} := { @@ -54,49 +55,41 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). Definition token `{Σ : cpp_logic, !lockedG Σ} (g : gname) (qt : cQp.t) : mpred := MutexTokens.token g.(token_gname) qt. - Definition given_token `{Σ : cpp_logic, !lockedG Σ} (g : gname) (qt : cQp.t) : mpred := - MutexTokens.given_token g.(token_gname) qt. #[global] Instance token_timeless `{Σ : cpp_logic, !lockedG Σ} g qt : Timeless (token g qt). Proof. rewrite /token. apply _. Qed. #[global] Instance token_WeaklyObjective `{Σ : cpp_logic, !lockedG Σ} g qt : WeaklyObjective (token g qt). Proof. rewrite /token /MutexTokens.token. apply _. Qed. - #[global] Instance given_token_timeless `{Σ : cpp_logic, !lockedG Σ} g qt : - Timeless (given_token g qt). - Proof. rewrite /given_token. apply _. Qed. - #[global] Instance given_token_WeaklyObjective `{Σ : cpp_logic, !lockedG Σ} g qt : - WeaklyObjective (given_token g qt). - Proof. rewrite /given_token /MutexTokens.given_token. apply _. Qed. - #[global] Hint Opaque token given_token : sl_opacity typeclass_instances. - - (** [owned_count_id_auth γ Some (th, n)] implies that the lock's count is [n + 1]. *) + #[global] Hint Opaque token : sl_opacity typeclass_instances. + + (** [owned_count_id_auth γ (Some (th, n, qt))] implies that the lock's count is [n + 1]. *) sl.lock Definition owned_count_id_auth `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (om : option (thread_idT * natO)) : mpred := + (γ : gname) (om : option (thread_idT * nat * cQp.t)) : mpred := own γ.(owned_count_id) (● (option_map Excl om)). #[only(timeless)] derive owned_count_id_auth. - (** [owned_count_id_frag γ Some (th, n)] implies that the lock's count is [n + 1]. *) + (** [owned_count_id_frag γ (Some (th, n, qt))] implies that the lock's count is [n + 1]. *) sl.lock Definition owned_count_id_frag `{Σ : cpp_logic, !lockedG Σ} - (γ : gname) (om : option (thread_idT * natO)) : mpred := + (γ : gname) (om : option (thread_idT * nat * cQp.t)) : mpred := own γ.(owned_count_id) (◯ (option_map Excl om)). #[only(timeless)] derive owned_count_id_frag. #[local] Open Scope nat_scope. (** For positive [n], [locked γ qt th n] records the physical lock count - through [owned_count_id_frag]. At zero it owns the namespace permission - and registration token. The first lock transfers that permission into - the physical invariant; the final unlock returns it. *) + and exact registration share through [owned_count_id_frag]. At zero it + owns the namespace permission and registration token. The first lock + deposits both in the physical invariant; the final unlock returns them. *) sl.lock Definition locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !lockedG Σ} (γ : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := match n with | 0 => MutexSets.my_mutexes th (CoPset $ ↑γ.(mutex_inv_namespace)) ** token γ qt - | S n => owned_count_id_frag γ (Some (th, n)) ** given_token γ qt + | S n => owned_count_id_frag γ (Some (th, n, qt)) end. #[only(timeless)] derive locked. @@ -121,9 +114,8 @@ Module RecursiveMutexAtomicSpecs (T : MutexCPPName). rewrite locked.unlock. iIntros "[H1 H2]". destruct n, m; try auto. iExFalso. - iDestruct "H1" as "[A _]". iDestruct "H2" as "[B _]". rewrite owned_count_id_frag.unlock. - iDestruct (own_valid_2 with "A B") as %HV; exfalso. + iDestruct (own_valid_2 with "H1 H2") as %HV; exfalso. rewrite -auth_frag_op auth_frag_valid in HV. done. Qed. @@ -210,12 +202,12 @@ cinv ( Definition R `{Σ : cpp_logic, σ : genv, MUTEX_SETS : !mutex_set_G Σ, !lockedG Σ} (γ : gname) (q : cQp.t) : Rep := type_ptrR T.cpp_ty ** as_Rep (fun this => cinv rmutex_N γ.(inv_gname) - (∃ owner count, this |-> rawR owner count ** + (∃ owner count qt, this |-> rawR owner count ** (* [owned_count_id_auth] stores [counter - 1]. *) - owned_count_id_auth γ ((λ t, (t, Nat.pred count)) <$> owner) ** + owned_count_id_auth γ ((λ t, (t, Nat.pred count, qt)) <$> owner) ** match owner with - | None => MutexTokens.token_full γ.(token_gname) - | Some th => MutexTokens.token_not_full γ.(token_gname) ** + | None => emp + | Some th => token γ qt ** MutexSets.my_mutexes th (CoPset $ ↑γ.(mutex_inv_namespace)) end)) ** @@ -274,13 +266,13 @@ cinv ( intros HE. rewrite R.unlock locked.unlock /token !_at_sep !_at_pureR _at_as_Rep. - iIntros "((_ & #HI & Hown) & Htoken & Hcount & _)". - iInv rmutex_N as "[(%owner & %count & _ & >Hauth & Hbalance) Hown]". + iIntros "((_ & #HI & Hown) & Htoken & Hcount)". + iInv rmutex_N as "[(%owner & %count & %held_qt & _ & >Hauth & Hheld) Hown]". destruct owner as [owner|]. - - iDestruct "Hbalance" as "[Hbalance _]". - iAssert (▷ False)%I with "[Hbalance Htoken]" as "Hfalse". - { iNext. iApply MutexTokens.token_not_full_full_token. iFrame. } - iMod "Hfalse" as %[]. + - iDestruct "Hheld" as "[>Hheld _]". + iEval (rewrite /token) in "Hheld". + iDestruct (MutexTokens.token_valid_2 with "[$Htoken $Hheld]") as %Hbad. + exfalso. exact (Qp.not_add_le_l 1 (cQp.frac held_qt) Hbad). - iEval (rewrite owned_count_id_auth.unlock /=) in "Hauth". iEval (rewrite owned_count_id_frag.unlock) in "Hcount". iDestruct (own_valid_2 with "Hauth Hcount") as %Hvalid. @@ -363,21 +355,13 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. Proof. rewrite /token /at_specs.token. apply _. Qed. #[global] Hint Opaque token : sl_opacity typeclass_instances. - Definition given_token `{Σ : cpp_logic, !G Σ} - (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := - own g.(level_gname) (◯E (S n, th, qt)). - #[global] Hint Opaque given_token : sl_opacity typeclass_instances. - #[global] Instance given_token_timeless `{Σ : cpp_logic, !G Σ} g qt th n : - Timeless (given_token g qt th n). - Proof. rewrite /given_token. apply _. Qed. - Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) : mpred := token g qt ** MutexSets.my_mutexes th (CoPset $ ↑rmutex_inv_namespace g). Definition locked `{Σ : cpp_logic, !G Σ, σ : genv} (_this : ptr) (g : gname) (qt : cQp.t) (th : thread_idT) (n : nat) : mpred := - given_token g qt th n. + own g.(level_gname) (◯E (S n, th, qt)). #[global] Instance not_locked_timeless `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ, σ : genv} this g qt th : Timeless (not_locked this g qt th). Proof. rewrite /not_locked. apply _. Qed. @@ -397,7 +381,7 @@ Module RecursiveMutexPreds (T : MutexCPPName) <: RECURSIVE_MUTEX_PREDS T. this |-> R g q P ** token g 1$m ** locked this g qt th n |-- (|={⊤}=> False). Proof. - rewrite /R /token /locked /given_token !_at_sep !_at_pureR inv_rmutex.unlock. + rewrite /R /token /locked !_at_sep !_at_pureR inv_rmutex.unlock. iIntros "((HR & Hown & #Hinv) & Htoken & Hheld)". iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hauth & Hcase) Hown]". iDestruct (own_valid_2 with "Hauth Hheld") as %[=]%excl_auth_agree_L; subst. @@ -492,7 +476,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** (∃ t, [| acquire n t |] ∗ ▷ acquireable this g qt th t P))%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token + rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /token /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". @@ -513,12 +497,12 @@ Section with_cpp. first apply (excl_auth_update _ _ (1, th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held 0 args). rewrite /locked /given_token /=. work. + iExists (Held 0 args). rewrite /locked /=. work. - iMod (own_update_2 with "Hn [$]") as "(Hg & ?)"; first apply (excl_auth_update _ _ (S (S n), th, qt)). wname [Preds.at_specs.locked _ qt th _] "Hlocked"; iMod ("Hclose" with "[$Hg $Hlocked //]") as "_"; iModIntro. - iExists (Held (S n) args). rewrite /locked /given_token /=. work. + iExists (Held (S n) args). rewrite /locked /=. work. Qed. Lemma at_spec_impl_na_spec_unlock this (xs : list val) (K : val -> mpred) : @@ -534,7 +518,7 @@ Section with_cpp. iExists q, qt, (cinv_own g.(cinv_gname) q ** acquireable this g qt th (release $ Held n args) P)%I. wname [bi_wand] "W"; wfocus (bi_wand _ _) "W". { work $usenamed=true. } - rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /given_token + rewrite inv_rmutex.unlock Spec.acquireable.unlock /not_locked /locked /token /rmutex_inv_namespace. work; iAcIntro; rewrite /commit_acc/=; work. iInv rmutex_namespace as "[(%n' & %th' & %qt' & >Hn & Hcases) ?]" "Hclose". @@ -545,7 +529,7 @@ Section with_cpp. iMod "Hclose'" as "_". iMod (own_update_2 with "Hn [$]") as "(Hg & Hcase)"; first apply (excl_auth_update _ _ (n, th, qt)). - rewrite /not_locked /locked /given_token /token + rewrite /not_locked /locked /token /rmutex_inv_namespace release.unlock; destruct n. 1: rewrite [in at_specs.locked _ _ _ 0]at_specs.locked.unlock /=. 1: iDestruct select (MutexSets.my_mutexes _ _ ** at_specs.token _ _) as "[? ?]". From cab2b7c5baa72c2ae97255a6c76dcd1299d3b936 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 19:13:38 +0000 Subject: [PATCH 100/109] [mutex/proof/lock_guard.v] now verifies the lock_guard template code --- rocq-brick-libstdcpp/proof/mutex/inc.hpp | 4 + .../proof/mutex/proof/lock_guard.v | 306 +++++++++++++++++- .../proof/mutex/spec/lock_guard.v | 13 +- rocq-brick-libstdcpp/proof/mutex/spec/mutex.v | 10 +- .../test/mutex/test_cpp_proof.v | 186 ++++++++++- 5 files changed, 484 insertions(+), 35 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/inc.hpp b/rocq-brick-libstdcpp/proof/mutex/inc.hpp index de27243d..0df2d109 100644 --- a/rocq-brick-libstdcpp/proof/mutex/inc.hpp +++ b/rocq-brick-libstdcpp/proof/mutex/inc.hpp @@ -1,6 +1,10 @@ #include +#include template class std::lock_guard; +template class std::lock_guard; +template class std::lock_guard; +template class std::lock_guard>; template class std::unique_lock; template class std::unique_lock; template class std::scoped_lock; diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index 1f965da0..d1355188 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -1,12 +1,178 @@ Require Import skylabs.auto.cpp.proof. Require Import skylabs.brick.libstdcpp.mutex.spec.mutex. +Require Import skylabs.brick.libstdcpp.mutex.spec.recursive_mutex. +Require Import skylabs.brick.libstdcpp.mutex.spec.unique_lock. +Require Import skylabs.brick.libstdcpp.shared_mutex.shared_mutex. Require Import skylabs.brick.libstdcpp.mutex.spec.lock_guard. +Require Import skylabs.brick.libstdcpp.mutex.requirements. Require Import skylabs.brick.libstdcpp.mutex.inc_hpp. Import linearity. +Import lock_guard R_unfold. + +(* Verification of the lock_guard template code. *) + +#[local] Hint Resolve fractional.UNSAFE_read_prim_learn : sl_opacity. +#[local] Hint Opaque do_lock : sl_opacity. + +Definition lock_guard_name (mutex : name) : name := + "std::lock_guard" .<< Atype (Tnamed mutex) >>. + +(* Pull the C++ lock_guard template AST. *) +Definition lock_guard_ctor_template : Ctor. +Proof. + let c := eval vm_compute in + (meta__source.(skylabs.lang.cpp.syntax.templates.msymbols) !! + (("std::lock_guard" .<< Atype (Tparam "_Mutex") >>) .:: + Nctor [Tref (Tparam "_Mutex")])) in + lazymatch c with + | Some (mcore.Template _ (Oconstructor ?ctor)) => exact ctor + end. +Defined. + +Definition lock_guard_ctor_subst (mutex : name) : subst.subst.t := + (∅ &: subst.subst._type_param .= {["_Mutex"%pstring := Tnamed mutex]} + &: subst.subst._resolved_parenlist .= + {[(Tref (Tnamed mutex), [Tref (Tnamed mutex)]) := + fun es => default (Eunsupported "lock_guard reference initializer" Tvoid) + (head es)]} + &: subst.subst._resolved_member_type .= + {[(Tnamed mutex, Nlocal (Nid "lock")) := + Tnamed (mutex .:: Nfunction function_qualifiers.N "lock" [])]} + &: subst.subst._resolved_call .= + {[(mutex .:: Nfunction function_qualifiers.N "lock" [], []) := + fun es => Emember_call false + (inl (mutex .:: Nfunction function_qualifiers.N "lock" [], + Direct, "void()"%cpp_type)) + (Emember true (Ethis (Tptr (Tnamed (lock_guard_name mutex)))) + (Nid "_M_device") false (Tref (Tnamed mutex))) es]})%lens. + +(* Template lock_guard constructor. *) +Definition lock_guard_ctor (mutex : name) : Ctor. +Proof. + let rec extract result := + let result := eval cbv -[compare.name_eq_dec compare.type_eq_dec] in result in + lazymatch result with + | inr ?ctor => exact ctor + | context C [compare.name_eq_dec ?n ?n] => + let result := context C [@left (n = n) (n ≠ n) eq_refl] in extract result + | context C [compare.type_eq_dec ?ty ?ty] => + let result := context C [@left (ty = ty) (ty ≠ ty) eq_refl] in extract result + end in + extract (subst_ap.subst.run2 + (subst_ap.subst.M2.subst_ctor meta__source lock_guard_ctor_template) + (lock_guard_ctor_subst mutex)). +Defined. + +(* Verify that [lock_guard_ctor] specialized to "std::mutex" is equal to the AST + of "std::lock_guard::lock_guard(std::mutex&)". + can take anything BasicLockable, and we test specializations with + , , , and >. *) +Lemma lock_guard_ctor_std_mutex_ast : + source.(symbols) !! "std::lock_guard::lock_guard(std::mutex&)"%cpp_name = + Some (Oconstructor (lock_guard_ctor "std::mutex")). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_ctor_std_recursive_mutex_ast : + source.(symbols) !! "std::lock_guard::lock_guard(std::recursive_mutex&)"%cpp_name = + Some (Oconstructor (lock_guard_ctor "std::recursive_mutex")). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_ctor_std_shared_mutex_ast : + source.(symbols) !! "std::lock_guard::lock_guard(std::shared_mutex&)"%cpp_name = + Some (Oconstructor (lock_guard_ctor "std::shared_mutex")). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_ctor_std_unique_lock_mutex_ast : + source.(symbols) !! "std::lock_guard>::lock_guard(std::unique_lock&)"%cpp_name = + Some (Oconstructor (lock_guard_ctor "std::unique_lock"%cpp_name)). +Proof. vm_compute. reflexivity. Qed. + +Definition lock_guard_struct (mutex : name) (sz al : N) : Struct := + Build_Struct [] + [mkMember (Nid "_M_device") (Tref (Tnamed mutex)) false None (Build_LayoutInfo 0)] + [] [] (lock_guard_name mutex .:: Ndtor) false None Unspecified sz al. + Section with_cpp. + Context `{Σ : cpp_logic, σ : genv}. + Context (tu : translation_unit) (mutex : name) (sz al : N). + Context {mutexT : Type} (mutexR : cQp.t -> mutexT -> Rep). + Context `{!BasicLockable (Tnamed mutex) mutexR}. + + Definition inner_lock_spec : mpred := + specify.exact.method (mutex .:: Nfunction function_qualifiers.N "lock" []) + function_qualifiers.N Tvoid [] (lock_basic_lockable (Tnamed mutex) mutexR). + #[local] Hint Opaque inner_lock_spec : sl_opacity. + + Hypothesis Hstruct : tu.(types) !! lock_guard_name mutex = + Some (Gstruct (lock_guard_struct mutex sz al)). + (* Assume the specific instantiation of lock_guard for some given mutex code + exists. *) + Hypothesis Hcode : + genv_tu.find_code (lock_guard_name mutex .:: Nctor [Tref (Tnamed mutex)]) tu = + Some (genv_tu.CTctor (inl (lock_guard_struct mutex sz al)) (lock_guard_ctor mutex)). + + (* Verification of lock_guard.ctor_spec against the template code (Hcode). *) + Lemma ctor_ok : + denoteModule tu ** inner_lock_spec |-- + lock_guard.ctor_spec (mutexR:=mutexR) (Tnamed mutex) tu. + Proof using Hcode Hstruct. + have Hfuel : exists n, NM.cardinal (tu.(types)) = S n. + { rewrite NM.cardinal_1. + have Hin := NM.elements_1 (NM.find_2 Hstruct). + destruct (NM.elements tu.(types)) eqn:Helems. + - inversion Hin. + - eexists. reflexivity. } + destruct Hfuel as [fuel Hfuel]. + match goal with |- _ |-- ?SP => + unshelve epose proof (tactics.VerifySpec.verify_spec_hint_obligation_1 + false false tu σ _ _ Σ _ _ + (genv_tu.CTctor (inl (lock_guard_struct mutex sz al)) (lock_guard_ctor mutex)) + SP ltac:(unfold lock_guard.ctor_spec, specify.template.ctor, + specify.exact.ctor; apply find_spec.unmaterialized_isspec) ltac:( + unfold mvcg.find_code_of_type; rewrite Hcode; + rewrite bool_decide_true; [constructor | reflexivity])) as Hverify + end. + iIntros "[#Htu #Hlock]". + iDestruct (Hverify with "Htu") as "(%Hmodels & Hverify)". + iApply "Hverify". + iClear "Hverify". + clear Hverify. + unfold lock_guard_ctor, lock_guard_struct, lock_guard_name. + repeat lazymatch goal with + | |- environments.envs_entails _ (wp_init_identity _ _ _ _) => fail + | _ => step + end. + PostCondition.hide_post. + iApply (identity.init_identity_struct' tu (lock_guard_name mutex) _ this Hmodels + (identity.Rec []) (identity.Rec []) emp emp + ltac:(cbn zeta; rewrite Hfuel; cbn [identity.derivations]; + rewrite Hstruct; constructor) + (identity.red_result _) (identity.red_result _)). + iSplit; first done. + iIntros "_". + unfold inner_lock_spec, specify.exact.method, lock_basic_lockable. + go. + rewrite wp_minvoke_O.unlock /wp_minvoke /wp_mfptr. + cbn. + go. + iApply (invoke.use_cptrR_method tu _ (Tnamed mutex) CC_C mutex QM + Tvoid Ar_Definite [] _ _ "void()"%cpp_type mp [] _ eq_refl with "Hlock"). + go. + iExists (q$m)%cQp, m, K. + iFrame. + go. + by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). + Qed. +End with_cpp. + + +(* Specialization of [ctor_ok] for , , , + and >, whatever that means. *) + +Section with_std_mutex. Context `{Σ : cpp_logic, σ : genv}. Context {HAS_THREADS : HasStdThreads Σ}. Context `{MUTEX_SETS : !mutex_set_G Σ}. @@ -15,36 +181,144 @@ Section with_cpp. Import lock_guard. Import R_unfold. - (* TODO: not ideal *) + Lemma ctor_std_mutex_ok : + denoteModule source ** mutex.lock_spec |-- + lock_guard.ctor_spec "std::mutex" source. + Proof. + apply (ctor_ok source "std::mutex" 8 8 + (fun q gP => mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_ctor_std_mutex_ast. + vm_compute. reflexivity. + Qed. + #[global] Instance UNSAFE_R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> learn_eq ==> learn_eq ==> fin_at) mutex.R). Proof. solve_learnable. Qed. - #[local] Hint Resolve fractional.UNSAFE_read_prim_learn : sl_opacity. #[local] Instance: `{SplitRecord (M T)} := {}. - (* std::mutex::lock() spec implies lock_guard ctor *) - Lemma ctor_ok : - denoteModule source ** - mutex.lock_spec - |-- lock_guard.ctor_spec "std::mutex" source. + Lemma dtor_ok : verify[source] "std::lock_guard::~lock_guard()". Proof. verify_spec. + rewrite !R.unlock. go. iExists K, t. go with br_erefl. by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). Qed. - (* std::mutex::unlock() spec implies lock_guard dtor *) - Lemma dtor_ok : verify[source] "std::lock_guard::~lock_guard()". +End with_std_mutex. + +Section with_std_recursive_mutex. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!std_recursive_mutex.G Σ}. + + Lemma ctor_std_recursive_mutex_ok : + denoteModule source ** std_recursive_mutex.std_lock_spec_alt |-- + lock_guard.ctor_spec "std::recursive_mutex" source. Proof. - verify_spec. - rewrite !R.unlock. - go. - iExists K, t. - go with br_erefl. - by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). + apply (ctor_ok source "std::recursive_mutex" 8 8 + (fun q gP => std_recursive_mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_ctor_std_recursive_mutex_ast. + vm_compute. reflexivity. Qed. +End with_std_recursive_mutex. -End with_cpp. +Section with_std_shared_mutex. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!shared_mutex.G Σ}. + + Lemma ctor_std_shared_mutex_ok : + denoteModule source ** shared_mutex.lock_spec |-- + lock_guard.ctor_spec "std::shared_mutex" source. + Proof. + apply (ctor_ok source "std::shared_mutex" 8 8 + (fun q gP => shared_mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_ctor_std_shared_mutex_ast. + vm_compute. reflexivity. + Qed. +End with_std_shared_mutex. + +Section with_std_unique_lock_mutex. + Context `{Σ : cpp_logic, σ : genv}. + Context {HAS_THREADS : HasStdThreads Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ}. + Context `{!mutex.G Σ}. + + Definition unique_lock_mutexR (_ : cQp.t) (_ : unit) : Rep := + type_ptrR "std::unique_lock". + #[local] Hint Opaque unique_lock_mutexR : sl_opacity. + + #[local] Abbreviation unique_lockR := + (unique_lock.R "std::mutex" (fun q gP => mutex.R gP.1 q gP.2)). + + Definition unique_lock_mutex_do_lock (this : ptr) (_ : unit) (K : mpred) : mpred := + ∃ mm, + this |-> unique_lockR 1$m (Some mm) ** + [| ~~ mm.(unique_lock.is_held) |] ** + do_lock "std::mutex" mm.(unique_lock.mutex_ptr) mm.(unique_lock.mutex_m) + (this |-> unique_lockR 1$m + (Some (mm &: unique_lock._is_held .= true)%lens) -* K). + + Definition unique_lock_mutex_do_unlock (this : ptr) (_ : unit) (K : mpred) : mpred := + ∃ mm, + this |-> unique_lockR 1$m (Some mm) ** + [| mm.(unique_lock.is_held) |] ** + do_unlock "std::mutex" mm.(unique_lock.mutex_ptr) mm.(unique_lock.mutex_m) + (this |-> unique_lockR 1$m + (Some (mm &: unique_lock._is_held .= false)%lens) -* K). + + #[local] Instance unique_lock_mutex_basic_lockable : + BasicLockable "std::unique_lock" unique_lock_mutexR := + {| do_lock := unique_lock_mutex_do_lock; + do_unlock := unique_lock_mutex_do_unlock |}. + + Lemma unique_lock_mutex_lock_spec : + unique_lock.lock_spec "std::mutex" source |-- + inner_lock_spec "std::unique_lock"%cpp_name unique_lock_mutexR. + Proof. + unfold unique_lock.lock_spec, inner_lock_spec. + rewrite specify_notation.template_specify.unlock. + cbn. + have -> : resolve_value source "std::unique_lock"%cpp_name = None + by vm_compute. + change (specify.exact.method "std::unique_lock::lock()" + function_qualifiers.N Tvoid [] + (\this this + \pre{mm} this |-> unique_lockR 1$m (Some mm) + \require ~~ mm.(unique_lock.is_held) + \pre{K} do_lock "std::mutex" mm.(unique_lock.mutex_ptr) + mm.(unique_lock.mutex_m) K + \post this |-> unique_lockR 1$m + (Some (mm &: unique_lock._is_held .= true)%lens) ** K) |-- + inner_lock_spec "std::unique_lock"%cpp_name unique_lock_mutexR). + iApply specify_mono; intros this xs Q. + unfold lock_basic_lockable, unique_lock_mutex_do_lock. + cbn. + ework with br_erefl. + Qed. + + Lemma ctor_std_unique_lock_mutex_ok : + denoteModule source ** unique_lock.lock_spec "std::mutex" source |-- + lock_guard.ctor_spec (mutexR := unique_lock_mutexR) + "std::unique_lock" source. + Proof. + rewrite unique_lock_mutex_lock_spec. + apply (ctor_ok source "std::unique_lock"%cpp_name 8 8 + unique_lock_mutexR). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_ctor_std_unique_lock_mutex_ast. + vm_compute. reflexivity. + Qed. +End with_std_unique_lock_mutex. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 99ea3081..2a6d08ba 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -74,15 +74,20 @@ NES.Begin lock_guard. #[global] Instance R_learn : LearnEqF1 R := ltac:(solve_learnable). - cpp.spec "std::lock_guard<$ty>::lock_guard($ty&)" as ctor_spec from source templates templates ( - \\with + (* Register the instantiated signature directly so it can also be verified + with a symbolic mutex type. Keep [tu] for the existing spec interface. *) + Definition ctor_spec (tu : translation_unit) : mpred := + specify.template.ctor ("std::lock_guard" .<< Atype ty >>) [Tref ty] $ \this this \arg{mp} "" (Vptr mp) \pre{q m} mp |-> mutexR q$m m \pre{K} do_lock ty mp m K \post - this |-> R 1$m (Mk mp q m) ** K - ). + this |-> R 1$m (Mk mp q m) ** K. + #[global] Hint Opaque ctor_spec : sl_opacity. + #[global] Arguments ctor_spec : simpl never. + Definition SpecFor_ctor_spec := RegisterSpec (ctor_spec source). + #[global] Existing Instance SpecFor_ctor_spec. cpp.spec "std::lock_guard<$ty>::~lock_guard()" as dtor_spec from source templates templates ( \\with diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v index d823c111..544c6eb6 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/mutex.v @@ -87,8 +87,8 @@ Module Type MUTEX_PREDS. Context `{!G Σ}. #[global] Hint Opaque R : sl_opacity typeclass_instances. #[only(cfractional,cfracvalid,ascfractional)] derive R. - (* #[global] Declare Instance R_type_ptr : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv} g q P, - Typed class_name (R g q P). *) + #[global] Declare Instance R_type_ptr : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv} g q P, + Observe (type_ptrR cpp_ty) (R g q P). #[global] Declare Instance R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) R). @@ -298,7 +298,7 @@ End mutex_spec. bind them to their C++ names. *) Module StdMutex (Preds : MUTEX_PREDS with Definition cpp_ty := "std::mutex"%cpp_type). Include Preds. - #[global] Hint Opaque token not_locked locked R : sl_opacity. + #[global] Hint Opaque cpp_ty token not_locked locked R : sl_opacity. Module Spec := mutex_spec Preds. @@ -320,6 +320,10 @@ Module StdMutex (Preds : MUTEX_PREDS with Definition cpp_ty := "std::mutex"%cpp_ Context `{MOD : source ⊧ σ}. Context {HAS_THREADS : HasStdThreads Σ}. + #[global] Instance R_std_type_ptr g q P : + Observe (type_ptrR "std::mutex") (R g q P). + Proof. apply R_type_ptr. Qed. + cpp.spec "std::mutex::mutex()" as ctor_spec with (\exact Reduce (Spec.ctor_spec)). diff --git a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v index 53c73e3e..ad6aa3c3 100644 --- a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v @@ -1,45 +1,176 @@ Require Import skylabs.auto.cpp.prelude.proof. Require Import skylabs.brick.libstdcpp.mutex.spec. +Require Import skylabs.brick.libstdcpp.lib.lock_ghost2. Require Import skylabs.brick.libstdcpp.test.mutex.test_cpp. Import linearity. Section with_cpp. Context `{Σ : cpp_logic, σ : genv} {HAS_THREADS : HasStdThreads Σ}. - Context `{!lockG Σ}. + Context `{MUTEX_SETS : !mutex_set_G Σ, !mutex.G Σ}. - cpp.spec "test_mutex()" as test_mutex_spec from source with (\post emp). + cpp.spec "test_mutex()" as test_mutex_spec from source with ( + \persist{thr} current_thread thr + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) + \post emp). Theorem test_mutex_ok : verify[source] "test_mutex()". Proof. verify_spec; go. - iExists emp. go. + iExists emp. + let rec expose_init := + first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" + | progress go1; expose_init] in + expose_init. + iMod "Hinit" as (g) "[Hm Htoken]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + iAssert (mutex.not_locked m_addr g thr (1$m)%cQp) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /mutex.not_locked. iFrame. } + go $usenamed=true. + iExists (mutex.locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iExists (mutex.not_locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iDestruct select (mutex.not_locked _ _ _ _) as "Hnotlocked". + iEval (rewrite /mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$Hrest $Hname]") as "Hnames". + go $usenamed=true. Qed. - cpp.spec "test_lock_guard()" as test_lock_guard_spec from source with (\post emp). + cpp.spec "test_lock_guard()" as test_lock_guard_spec from source with ( + \persist{thr} current_thread thr + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) + \post emp). Lemma test_lock_guard_ok : verify[source] "test_lock_guard()". Proof. verify_spec; go. - iExists emp; go. + iExists emp. + let rec expose_init := + first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" + | progress go1; expose_init] in + expose_init. + iMod "Hinit" as (g) "[Hm Htoken]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + iAssert (mutex.not_locked m_addr g thr (1$m)%cQp) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /mutex.not_locked. iFrame. } + go $usenamed=true. + iExists (mutex.locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iExists (mutex.not_locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iDestruct select (mutex.not_locked _ _ _ _) as "Hnotlocked". + iEval (rewrite /mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$Hrest $Hname]") as "Hnames". + go $usenamed=true. Qed. - cpp.spec "test_scoped_lock()" as test_scoped_lock_spec from source with (\post emp). + cpp.spec "test_scoped_lock()" as test_scoped_lock_spec from source with ( + \persist{thr} current_thread thr + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) + \post emp). Lemma test_scoped_lock_ok : verify[source] "test_scoped_lock()". Proof. verify_spec; go. iExists emp; go. - iExists emp; go. + iExists emp. + let rec open_init := + first [(iDestruct select ((|={⊤}=> _)%I) as "Hinit"; + iMod "Hinit" as (g2) "[Hm2 Htoken2]") + | progress go1; open_init] in + open_init. + iDestruct select ((|={⊤}=> _)%I) as "Hinit". + iMod "Hinit" as (g1) "[Hm1 Htoken1]". + iMod (mutex.init_R m1_addr g1 (nroot .@@ "test_scoped_lock" .@ "first") emp + ltac:(apply _) with "[$Hm1 $Htoken1]") as (g1') "(%HN1 & Hm1 & Htoken1)". + { done. } + iMod (mutex.init_R m2_addr g2 (nroot .@@ "test_scoped_lock" .@ "second") emp + ltac:(apply _) with "[$Hm2 $Htoken2]") as (g2') "(%HN2 & Hm2 & Htoken2)". + { done. } + have Hdisjoint : (↑mutex.mutex_inv_namespace g1' : coPset) ## + ↑mutex.mutex_inv_namespace g2'. + { rewrite HN1 HN2. solve_ndisj. } + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g1') ltac:(set_solver) + with "[$]") as "[Hrest Hname1]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr + (⊤ ∖ ↑mutex.mutex_inv_namespace g1') + (↑mutex.mutex_inv_namespace g2') ltac:(set_solver) + with "Hrest") as "[Hrest Hname2]". + iAssert (mutex.not_locked m1_addr g1' thr (1$m)%cQp) + with "[Htoken1 Hname1]" as "Hnotlocked1". + { rewrite /mutex.not_locked. iFrame. } + iAssert (mutex.not_locked m2_addr g2' thr (1$m)%cQp) + with "[Htoken2 Hname2]" as "Hnotlocked2". + { rewrite /mutex.not_locked. iFrame. } + go $usenamed=true. + iExists (mutex.locked m1_addr g1' thr (1$m)%cQp), + (mutex.locked m2_addr g2' thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. + go $usenamed=true. + iExists (mutex.not_locked m1_addr g1' thr (1$m)%cQp), + (mutex.not_locked m2_addr g2' thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. + go $usenamed=true. + iDestruct select (mutex.not_locked m1_addr _ _ _) as "Hnotlocked1". + iDestruct select (mutex.not_locked m2_addr _ _ _) as "Hnotlocked2". + iEval (rewrite /mutex.not_locked) in "Hnotlocked1". + iEval (rewrite /mutex.not_locked) in "Hnotlocked2". + iDestruct "Hnotlocked1" as "[Htoken1 Hname1]". + iDestruct "Hnotlocked2" as "[Htoken2 Hname2]". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr + (⊤ ∖ ↑mutex.mutex_inv_namespace g1') + (↑mutex.mutex_inv_namespace g2') ltac:(set_solver) + with "[$Hrest $Hname2]") as "Hrest". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g1') ltac:(set_solver) + with "[$Hrest $Hname1]") as "Hnames". + go $usenamed=true. Qed. - cpp.spec "test_unique_lock()" as test_unique_lock_spec from source with (\post emp). + cpp.spec "test_unique_lock()" as test_unique_lock_spec from source with ( + \persist{thr} current_thread thr + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) + \post emp). Lemma test_unique_lock_ok : verify[source] "test_unique_lock()". Proof. verify_spec; go. - - iExists emp; go. + iExists emp. + let rec expose_init := + first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" + | progress go1; expose_init] in + expose_init. + iMod "Hinit" as (g) "[Hm Htoken]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + iAssert (mutex.not_locked m_addr g thr (1$m)%cQp) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /mutex.not_locked. iFrame. } + go $usenamed=true. + iExists (mutex.locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iExists (mutex.not_locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iDestruct select (mutex.not_locked _ _ _ _) as "Hnotlocked". + iEval (rewrite /mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$Hrest $Hname]") as "Hnames". + go $usenamed=true. Qed. @@ -50,17 +181,48 @@ Section with_cpp. Lemma test_unique_lock_defer_ok : verify[source] "test_unique_lock_defer()". Proof. verify_spec; go. - iExists emp; go. + iExists emp. + let rec open_init := + first [(iDestruct select ((|={⊤}=> _)%I) as "Hinit"; + iMod "Hinit" as (g) "[Hm Htoken]") + | progress go1; open_init] in + open_init. + go $usenamed=true. Qed. cpp.spec "std::move&>(std::unique_lock&)" from source inline. cpp.spec "test_unique_lock_move()" as test_unique_lock_move_spec from source with ( + \persist{thr} current_thread thr + \prepost MutexSets.my_mutexes thr (coPset.CoPset ⊤) \post emp). Lemma test_unique_lock_move_ok : verify[source] "test_unique_lock_move()". Proof. verify_spec; go. - iExists emp; go. + iExists emp. + let rec expose_init := + first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" + | progress go1; expose_init] in + expose_init. + iMod "Hinit" as (g) "[Hm Htoken]". + iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$]") as "[Hrest Hname]". + iAssert (mutex.not_locked m_addr g thr (1$m)%cQp) + with "[Htoken Hname]" as "Hnotlocked". + { rewrite /mutex.not_locked. iFrame. } + go $usenamed=true. + iExists (mutex.locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iExists (mutex.not_locked m_addr g thr (1$m)%cQp), (1$m)%cQp. + go $usenamed=true. + iDestruct select (mutex.not_locked _ _ _ _) as "Hnotlocked". + iEval (rewrite /mutex.not_locked) in "Hnotlocked". + iDestruct "Hnotlocked" as "[Htoken Hname]". + iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ + (↑mutex.mutex_inv_namespace g) ltac:(set_solver) + with "[$Hrest $Hname]") as "Hnames". + go $usenamed=true. Qed. End with_cpp. From a3a1674a1b4f286f4772d6aec91956af38d490df Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 19:57:17 +0000 Subject: [PATCH 101/109] add and for all 3 kinds of mutexes --- .../proof/mutex/spec/recursive_mutex.v | 9 ++ .../test/mutex/custom_mutex.hpp | 12 ++- .../test/mutex/custom_mutex_hpp_proof.v | 30 ++++++ .../test/mutex/custom_recursive_mutex.hpp | 16 +++ .../mutex/custom_recursive_mutex_hpp_proof.v | 52 ++++++++- .../test/mutex/test_cpp_proof.v | 100 ++++++++---------- .../test/shared_mutex/custom_shared_mutex.hpp | 12 +++ .../custom_shared_mutex_hpp_proof.v | 45 +++++++- 8 files changed, 214 insertions(+), 62 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v index 97830311..291148a5 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/recursive_mutex.v @@ -181,6 +181,15 @@ Module recursive_mutex_spec (T : MutexCPPName) \pre{qt n args} acquireable this g qt th (Held n args) P \post acquireable this g qt th (release $ Held n args) P). + Definition try_lock_spec : ptr -> WpSpec mpred val val := + (\this this + \prepost{g TT P q} this |-> R g q (∃ xs, @tele_app TT (fun _ => mpred) P xs) + \persist{th} current_thread th + \pre{qt n} acquireable this g qt th n P + \post{b}[Vbool b] + if b then Exists n', [| acquire n n' |] ** ▷ acquireable this g qt th n' P + else acquireable this g qt th n P). + Definition do_lock (this : ptr) (lk : gname * mpred) (K : mpred) : mpred := ∃ TT P qt th n, [| lk.2 = (∃ xs, @tele_app TT (fun _ => mpred) P xs)%I |] ** diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp index 54865952..c949f418 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex.hpp @@ -37,7 +37,7 @@ class MyMutex { } public: - MyMutex() {} + MyMutex() noexcept {} void lock() { std::thread::id this_id{std::this_thread::get_id()}; @@ -55,6 +55,16 @@ class MyMutex { m_owner = this_id; } + bool try_lock() noexcept { + std::thread::id this_id{std::this_thread::get_id()}; + if (m_lock.exchange(1)) + return false; + + assert(m_owner == std::thread::id()); + m_owner = this_id; + return true; + } + void unlock(){ assert(m_owner == std::this_thread::get_id()); m_owner = std::thread::id(); // unowned diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index fd94f9fd..552baa9a 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -392,6 +392,24 @@ Module custom_mutex. \exact Reduce (Spec.unlock_spec this)). + cpp.spec "MyMutex::try_lock()" as try_lock_spec_alt with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_spec_alt this)). + + cpp.spec "MyMutex::try_lock()" as try_lock_spec with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_spec this)). + + Lemma try_lock_spec_equiv_try_lock_spec_alt : try_lock_spec ⊣⊢ try_lock_spec_alt. + Proof. + rewrite /try_lock_spec /try_lock_spec_alt /specify. + do 2 f_equiv. intros this xs K. + rewrite !add_with_equiv. f_equiv=> qg. + rewrite !add_prepost_equiv Spec.try_lock_spec_equiv_try_lock_spec_alt; done. + Qed. + Lemma lock_spec_equiv_lock_spec_alt : lock_spec ⊣⊢ lock_spec_alt. Proof. rewrite /lock_spec /lock_spec_alt /specify. @@ -558,6 +576,18 @@ Module custom_mutex. Unshelve. all: exact (1$m)%cQp. Qed. + Lemma mymutex_try_lock_alt_proof : verify[source] try_lock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec; ego. + repeat (first [case_bool_decide | wp_if]; ego). + Qed. + + Lemma mymutex_try_lock_proof : verify[source] try_lock_spec. + Proof using MOD HAS_THREADS. + rewrite try_lock_spec_equiv_try_lock_spec_alt. + exact mymutex_try_lock_alt_proof. + Qed. + Lemma mymutex_unlock_alt_proof : verify[source] unlock_spec_alt. Proof using MOD HAS_THREADS. verify_spec. diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp index 9ff97339..fa6dc621 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp @@ -57,6 +57,22 @@ class MyRecursiveMutex { } m_count++; } + + bool try_lock() noexcept { + size_t this_id = std::hash{}(std::this_thread::get_id()); + if (m_owner != this_id) { + if (!m_lock.try_lock()) + return false; + m_owner = this_id; + } + + // At the recursion limit, leave ownership and the count unchanged. + if (m_count + 1 == 0) + return false; + m_count++; + return true; + } + // TODO: connect token and given token to the exclusive token somehow, so that void unlock() { diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v index 033e4093..d44b40f5 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex_hpp_proof.v @@ -211,6 +211,11 @@ Module custom_recursive_mutex. \prepost{qg} globals qg \exact Reduce (Spec.unlock_spec_alt this)). + cpp.spec "MyRecursiveMutex::try_lock()" as try_lock_spec from source with + (\this this + \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_spec this)). + (* Helper specs, proved later. *) cpp.spec "std::mutex::lock()" as inner_lock_spec with ( \this inner @@ -220,6 +225,16 @@ Module custom_recursive_mutex. \pre{qt} not_locked container g qt th \post protected container g.(owner_gname) P ** mutex.locked inner g.(inner_gname) th qt). + cpp.spec "std::mutex::try_lock()" as inner_try_lock_spec with ( + \this inner + \prepost{(container : ptr) g q P} container |-> R g q P + \require inner = container ,, _field "MyRecursiveMutex::m_lock" + \persist{th} current_thread th + \pre{qt} not_locked container g qt th + \post{b}[Vbool b] + if b then protected container g.(owner_gname) P ** mutex.locked inner g.(inner_gname) th qt + else not_locked container g qt th). + cpp.spec "std::mutex::unlock()" as inner_unlock_spec with ( \this inner \prepost{(container : ptr) g q P} container |-> R g q P @@ -308,6 +323,12 @@ Module custom_recursive_mutex. iExists qt. work with br_erefl. Qed. + Lemma inner_try_lock_spec_ok : mutex.try_lock_spec_alt |-- inner_try_lock_spec. + Proof. + apply specify_mono. rewrite /R /not_locked. work with br_erefl. + iExists qt. work with br_erefl. + Qed. + Lemma inner_unlock_spec_ok : mutex.unlock_spec_alt |-- inner_unlock_spec. Proof. apply specify_mono. rewrite /R /not_locked. work with br_erefl. @@ -513,6 +534,31 @@ Module custom_recursive_mutex. rewrite Hcount. iFrame "#∗". Qed. + Lemma try_lock_proof : verify[source] try_lock_spec. + Proof using MOD. + verify_spec. + rewrite Spec.acquireable.unlock. destruct n as [|n xs]. + - ego. + match goal with b : bool |- _ => destruct b end; ego. + + rewrite /protected. ego. + iDestruct select (this |-> R _ _ _) as "HR". + iDestruct select (owner_tid_frag _ None) as "Hfrag". + iDestruct select (mutex.locked _ _ _ _) as "Hinner". + iDestruct (store_owner_action with "[$HR $Hfrag $Hinner]") as "Hstore". + iSplitL "Hstore"; first iExact "Hstore". ego. + rewrite /countR. ego. + rewrite Spec.acquireable.unlock /locked /countR. ego. + wname [P] "HP". iExact "HP". + + rewrite Spec.acquireable.unlock. ego. + - rewrite /locked /countR. ego. + destruct (decide (Z.of_nat (S n) = 18446744073709551615)%Z) as [Hmax|Hmax]; go. + + rewrite Hmax. ego. + rewrite Spec.acquireable.unlock /locked /countR Hmax. iFrame "#∗". + + ego. rewrite Spec.acquireable.unlock /locked /countR. + have Hcount : (Z.of_nat (S n) + 1)%Z = Z.of_nat (S (S n)) by lia. + rewrite Hcount. iFrame "#∗". + Qed. + Lemma unlock_proof : verify[source] unlock_spec. Proof using MOD. verify_spec. @@ -556,7 +602,7 @@ Module custom_recursive_mutex. Definition standard_library_specs : mpred := denoteModule thread_hpp.source ** mutex.ctor_spec ** mutex.dtor_spec ** - mutex.lock_spec_alt ** mutex.unlock_spec_alt ** + mutex.lock_spec_alt ** mutex.unlock_spec_alt ** mutex.try_lock_spec_alt ** std.atomic.ctor Tulong ** std.atomic.dtor Tulong ** std.atomic.cast Tulong ** std.atomic.assign Tulong ** thread_id_specs.get_id_spec ** @@ -566,7 +612,7 @@ Module custom_recursive_mutex. Lemma link `{count_objective : !forall this, WeaklyObjective (countR this 0)} : denoteModule source ** standard_library_specs |-- - ctor_spec ** dtor_spec ** lock_spec ** unlock_spec. + ctor_spec ** dtor_spec ** lock_spec ** unlock_spec ** try_lock_spec. Proof using MOD. rewrite /standard_library_specs. iIntros "[Hsource [#Hthread Hspecs]]". @@ -577,8 +623,10 @@ Module custom_recursive_mutex. wapply dtor_proof. wapply lock_proof. wapply unlock_proof. + wapply try_lock_proof. wapply inner_lock_spec_ok. wapply inner_unlock_spec_ok. + wapply inner_try_lock_spec_ok. wapply id_dtor_ok. wapply (thread_id_specs.hash_link (MOD := THREADMOD)). work. diff --git a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v index ad6aa3c3..48038d57 100644 --- a/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/test_cpp_proof.v @@ -17,12 +17,10 @@ Section with_cpp. Theorem test_mutex_ok : verify[source] "test_mutex()". Proof. verify_spec; go. - iExists emp. - let rec expose_init := - first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" - | progress go1; expose_init] in - expose_init. - iMod "Hinit" as (g) "[Hm Htoken]". + iExists (nroot .@@ "test_mutex"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g) "(%HN & Hm & Htoken)". iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". @@ -51,12 +49,10 @@ Section with_cpp. Lemma test_lock_guard_ok : verify[source] "test_lock_guard()". Proof. verify_spec; go. - iExists emp. - let rec expose_init := - first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" - | progress go1; expose_init] in - expose_init. - iMod "Hinit" as (g) "[Hm Htoken]". + iExists (nroot .@@ "test_lock_guard"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g) "(%HN & Hm & Htoken)". iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". @@ -85,43 +81,37 @@ Section with_cpp. Lemma test_scoped_lock_ok : verify[source] "test_scoped_lock()". Proof. verify_spec; go. - iExists emp; go. - iExists emp. - let rec open_init := - first [(iDestruct select ((|={⊤}=> _)%I) as "Hinit"; - iMod "Hinit" as (g2) "[Hm2 Htoken2]") - | progress go1; open_init] in - open_init. + iExists (nroot .@@ "test_scoped_lock" .@ "first"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + go. + iExists (nroot .@@ "test_scoped_lock" .@ "second"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g2) "(%HN2 & Hm2 & Htoken2)". iDestruct select ((|={⊤}=> _)%I) as "Hinit". - iMod "Hinit" as (g1) "[Hm1 Htoken1]". - iMod (mutex.init_R m1_addr g1 (nroot .@@ "test_scoped_lock" .@ "first") emp - ltac:(apply _) with "[$Hm1 $Htoken1]") as (g1') "(%HN1 & Hm1 & Htoken1)". - { done. } - iMod (mutex.init_R m2_addr g2 (nroot .@@ "test_scoped_lock" .@ "second") emp - ltac:(apply _) with "[$Hm2 $Htoken2]") as (g2') "(%HN2 & Hm2 & Htoken2)". - { done. } - have Hdisjoint : (↑mutex.mutex_inv_namespace g1' : coPset) ## - ↑mutex.mutex_inv_namespace g2'. + iMod "Hinit" as (g1) "(%HN1 & Hm1 & Htoken1)". + have Hdisjoint : (↑mutex.mutex_inv_namespace g1 : coPset) ## + ↑mutex.mutex_inv_namespace g2. { rewrite HN1 HN2. solve_ndisj. } iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ - (↑mutex.mutex_inv_namespace g1') ltac:(set_solver) + (↑mutex.mutex_inv_namespace g1) ltac:(set_solver) with "[$]") as "[Hrest Hname1]". iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr - (⊤ ∖ ↑mutex.mutex_inv_namespace g1') - (↑mutex.mutex_inv_namespace g2') ltac:(set_solver) + (⊤ ∖ ↑mutex.mutex_inv_namespace g1) + (↑mutex.mutex_inv_namespace g2) ltac:(set_solver) with "Hrest") as "[Hrest Hname2]". - iAssert (mutex.not_locked m1_addr g1' thr (1$m)%cQp) + iAssert (mutex.not_locked m1_addr g1 thr (1$m)%cQp) with "[Htoken1 Hname1]" as "Hnotlocked1". { rewrite /mutex.not_locked. iFrame. } - iAssert (mutex.not_locked m2_addr g2' thr (1$m)%cQp) + iAssert (mutex.not_locked m2_addr g2 thr (1$m)%cQp) with "[Htoken2 Hname2]" as "Hnotlocked2". { rewrite /mutex.not_locked. iFrame. } go $usenamed=true. - iExists (mutex.locked m1_addr g1' thr (1$m)%cQp), - (mutex.locked m2_addr g2' thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. + iExists (mutex.locked m1_addr g1 thr (1$m)%cQp), + (mutex.locked m2_addr g2 thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. go $usenamed=true. - iExists (mutex.not_locked m1_addr g1' thr (1$m)%cQp), - (mutex.not_locked m2_addr g2' thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. + iExists (mutex.not_locked m1_addr g1 thr (1$m)%cQp), + (mutex.not_locked m2_addr g2 thr (1$m)%cQp), (1$m)%cQp, (1$m)%cQp. go $usenamed=true. iDestruct select (mutex.not_locked m1_addr _ _ _) as "Hnotlocked1". iDestruct select (mutex.not_locked m2_addr _ _ _) as "Hnotlocked2". @@ -130,11 +120,11 @@ Section with_cpp. iDestruct "Hnotlocked1" as "[Htoken1 Hname1]". iDestruct "Hnotlocked2" as "[Htoken2 Hname2]". iDestruct (MutexSets.my_mutexes_join_mutex_name thr - (⊤ ∖ ↑mutex.mutex_inv_namespace g1') - (↑mutex.mutex_inv_namespace g2') ltac:(set_solver) + (⊤ ∖ ↑mutex.mutex_inv_namespace g1) + (↑mutex.mutex_inv_namespace g2) ltac:(set_solver) with "[$Hrest $Hname2]") as "Hrest". iDestruct (MutexSets.my_mutexes_join_mutex_name thr ⊤ - (↑mutex.mutex_inv_namespace g1') ltac:(set_solver) + (↑mutex.mutex_inv_namespace g1) ltac:(set_solver) with "[$Hrest $Hname1]") as "Hnames". go $usenamed=true. Qed. @@ -147,12 +137,10 @@ Section with_cpp. Lemma test_unique_lock_ok : verify[source] "test_unique_lock()". Proof. verify_spec; go. - iExists emp. - let rec expose_init := - first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" - | progress go1; expose_init] in - expose_init. - iMod "Hinit" as (g) "[Hm Htoken]". + iExists (nroot .@@ "test_unique_lock"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g) "(%HN & Hm & Htoken)". iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". @@ -181,12 +169,10 @@ Section with_cpp. Lemma test_unique_lock_defer_ok : verify[source] "test_unique_lock_defer()". Proof. verify_spec; go. - iExists emp. - let rec open_init := - first [(iDestruct select ((|={⊤}=> _)%I) as "Hinit"; - iMod "Hinit" as (g) "[Hm Htoken]") - | progress go1; open_init] in - open_init. + iExists (nroot .@@ "test_unique_lock_defer"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g) "(%HN & Hm & Htoken)". go $usenamed=true. Qed. @@ -200,12 +186,10 @@ Section with_cpp. Lemma test_unique_lock_move_ok : verify[source] "test_unique_lock_move()". Proof. verify_spec; go. - iExists emp. - let rec expose_init := - first [iDestruct select ((|={⊤}=> _)%I) as "Hinit" - | progress go1; expose_init] in - expose_init. - iMod "Hinit" as (g) "[Hm Htoken]". + iExists (nroot .@@ "test_unique_lock_move"), emp; go. + iSplitR; first (iPureIntro; rewrite namespaces.ndot_unseal; discriminate). + iIntros "Hinit". + iMod "Hinit" as (g) "(%HN & Hm & Htoken)". iDestruct (MutexSets.my_mutexes_alloc_mutex_name thr ⊤ (↑mutex.mutex_inv_namespace g) ltac:(set_solver) with "[$]") as "[Hrest Hname]". diff --git a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp index 744f5d33..bfbd01a2 100644 --- a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp +++ b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex.hpp @@ -17,6 +17,11 @@ class MySharedMutex { { idle = 0; } } + bool try_lock() noexcept { + int idle = 0; + return sharers.compare_exchange_strong(idle, -1); + } + void unlock() { sharers = 0; } @@ -29,6 +34,13 @@ class MySharedMutex { !sharers.compare_exchange_strong(val, val + 1)); } + bool try_lock_shared() noexcept { + int val = sharers; + if (val < 0 || val == INT_MAX) + return false; + return sharers.compare_exchange_strong(val, val + 1); + } + void unlock_shared() { sharers--; } diff --git a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v index f3ac8421..9e287cab 100644 --- a/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/shared_mutex/custom_shared_mutex_hpp_proof.v @@ -482,6 +482,24 @@ Module custom_shared_mutex. (\this this \prepost{qg} globals qg \exact Reduce (Spec.unlock_shared_spec this)). + cpp.spec "MySharedMutex::try_lock()" as try_lock_spec_alt with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_spec_alt this)). + cpp.spec "MySharedMutex::try_lock()" as try_lock_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_spec this)). + cpp.spec "MySharedMutex::try_lock_shared()" as try_lock_shared_spec with + (\this this \prepost{qg} globals qg + \exact Reduce (Spec.try_lock_shared_spec this)). + + Lemma try_lock_spec_equiv_try_lock_spec_alt : try_lock_spec ⊣⊢ try_lock_spec_alt. + Proof. + rewrite /try_lock_spec /try_lock_spec_alt /specify. + do 2 f_equiv. intros this xs K. rewrite !add_with_equiv. + f_equiv=> qg. + rewrite !add_prepost_equiv Spec.try_lock_spec_equiv_try_lock_spec_alt; done. + Qed. + Lemma lock_spec_equiv_lock_spec_alt : lock_spec ⊣⊢ lock_spec_alt. Proof. rewrite /lock_spec /lock_spec_alt /specify. @@ -746,6 +764,29 @@ Module custom_shared_mutex. Unshelve. all: exact 1%Qp. Qed. + Lemma mysharedmutex_try_lock_alt_proof : verify[source] try_lock_spec_alt. + Proof using MOD HAS_THREADS. + verify_spec; go. + Qed. + + Lemma mysharedmutex_try_lock_proof : verify[source] try_lock_spec. + Proof using MOD HAS_THREADS. + rewrite try_lock_spec_equiv_try_lock_spec_alt. exact mysharedmutex_try_lock_alt_proof. + Qed. + + Lemma mysharedmutex_try_lock_shared_proof : verify[source] try_lock_shared_spec. + Proof using MOD HAS_THREADS. + verify_spec; go. + repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + all: try match goal with + | Hb : -1 <= ?v |- _ => + have Hrange : 0 <= v < max_readers by unfold max_readers; lia + end. + all: try (ework with br_erefl; go). + all: repeat (first [case_bool_decide | match goal with b : bool |- _ => destruct b end | wp_if]; go). + Unshelve. all: exact 1%Qp. + Qed. + Lemma mysharedmutex_unlock_shared_proof : verify[source] unlock_shared_spec. Proof using MOD HAS_THREADS. verify_spec; go. @@ -812,7 +853,7 @@ Module custom_shared_mutex. Lemma link : denoteModule source ** standard_library_specs |-- ctor_spec ** dtor_spec ** lock_spec ** unlock_spec ** - lock_shared_spec ** unlock_shared_spec. + lock_shared_spec ** unlock_shared_spec ** try_lock_spec ** try_lock_shared_spec. Proof using MOD HAS_THREADS. rewrite /standard_library_specs. work. @@ -822,6 +863,8 @@ Module custom_shared_mutex. wapply mysharedmutex_unlock_proof. wapply mysharedmutex_lock_shared_proof. wapply mysharedmutex_unlock_shared_proof. + wapply mysharedmutex_try_lock_proof. + wapply mysharedmutex_try_lock_shared_proof. work. Qed. End with_Σ. From 5f603a01b6d6a4636155d1e12fdd43d1572328be Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 20:19:26 +0000 Subject: [PATCH 102/109] add libcpp_tests for custom_mutex implementation --- .../test/mutex/libcpp_tests/LICENSE.TXT | 311 ++++++++++ .../test/mutex/libcpp_tests/README.txt | 51 ++ .../test/mutex/libcpp_tests/UPSTREAM.json | 93 +++ .../mutex/assign.compile.pass.cpp | 21 + .../mutex/ctor.copy.compile.pass.cpp | 21 + .../libcpp_tests/mutex/ctor.default.pass.cpp | 33 ++ .../mutex/libcpp_tests/mutex/lock.pass.cpp | 82 +++ .../libcpp_tests/mutex/try_lock.pass.cpp | 51 ++ .../recursive_mutex/assign.compile.pass.cpp | 21 + .../ctor.copy.compile.pass.cpp | 21 + .../recursive_mutex/ctor.default.pass.cpp | 30 + .../recursive_mutex/lock.pass.cpp | 111 ++++ .../recursive_mutex/try_lock.pass.cpp | 80 +++ .../test/mutex/libcpp_tests/run.py | 70 +++ .../shared_mutex/assign.compile.pass.cpp | 22 + .../shared_mutex/ctor.copy.compile.pass.cpp | 22 + .../shared_mutex/ctor.default.pass.cpp | 25 + .../libcpp_tests/shared_mutex/lock.pass.cpp | 108 ++++ .../shared_mutex/lock_shared.pass.cpp | 145 +++++ .../shared_mutex/try_lock.pass.cpp | 63 ++ .../shared_mutex/try_lock_shared.pass.cpp | 81 +++ .../libcpp_tests/support/make_test_thread.h | 45 ++ .../mutex/libcpp_tests/support/test_macros.h | 550 ++++++++++++++++++ 23 files changed, 2057 insertions(+) create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/LICENSE.TXT create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/README.txt create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/UPSTREAM.json create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/make_test_thread.h create mode 100644 rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/test_macros.h diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/LICENSE.TXT b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/LICENSE.TXT new file mode 100644 index 00000000..e159d283 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/LICENSE.TXT @@ -0,0 +1,311 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libc++ library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/README.txt b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/README.txt new file mode 100644 index 00000000..c95d47a6 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/README.txt @@ -0,0 +1,51 @@ +CAUTION: the [libcpp_tests] directory, including this README and the LLVM + LICENSE.TXT, are generated by LLM. + +Custom mutex runtime tests + +These 17 tests are adapted from LLVM libc++ llvmorg-22.1.8: + ca7933e47d3a3451d81e72ac174dcb5aa28b59d1 + + mutex/ 5 tests of MyMutex + recursive_mutex/ 5 tests of MyRecursiveMutex + shared_mutex/ 7 tests of MySharedMutex + +They check construction, prohibition of copying/assignment, exclusive locking, +recursive ownership, shared locking, and the nonblocking try-lock methods. +The .compile.pass.cpp files check type traits without creating an executable. + +Run from the repository root with Python 3 and Clang: + + python3 rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py + python3 rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py --stdlib=libc++ + +The default uses libstdc++. libc++ must be installed for the second command. +Both commands run the same LLVM test sources with different standard libraries. +These are custom-class tests; they do not run the standard-type baseline cases. + +To use GCC's default standard library: + + python3 rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py --cxx=g++ --stdlib=default + +To select a test or group, add a path substring, for example: + + --filter=recursive_mutex/lock.pass.cpp + +The runner also works from other working directories. It compiles in C++17 mode +with assertions enabled, gives each compilation 60 seconds and each executable +20 seconds, and returns a nonzero status on any failure. Build outputs go into a +temporary directory that is removed after the run. There is no dependency on +LLVM lit, a full LLVM checkout, or the earlier /tmp test workspace. + +UPSTREAM.json records the exact source URLs, original SHA-256 hashes, and the +adaptations. Only the tested mutex types and include directives were changed: +custom headers replace the standard mutex headers, unused includes are removed, +and required standard facilities are included directly. All upstream assertions +and the two support headers are retained. LICENSE.TXT contains the LLVM license. + +These are native C++ runtime tests. test/dune-gen.sh excludes this folder from +cpp2v AST generation; run them using run.py separately from the Rocq build. + +Passing these tests does not establish full standard conformance or cover all +thread schedules. In particular, they do not exercise hash collisions or the +recursive mutex's counter limit. diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/UPSTREAM.json b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/UPSTREAM.json new file mode 100644 index 00000000..cee2d2d0 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/UPSTREAM.json @@ -0,0 +1,93 @@ +{ + "project": "LLVM libc++", + "release": "llvmorg-22.1.8", + "commit": "ca7933e47d3a3451d81e72ac174dcb5aa28b59d1", + "adaptations": [ + "Include the repository custom mutex header using a relative path.", + "Replace the tested std:: mutex type with the matching MyMutex, MyRecursiveMutex, or MySharedMutex type.", + "Remove unused includes from test sources and directly include for std::begin/std::end.", + "Preserve every upstream assertion and the support headers." + ], + "files": { + "mutex/assign.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.pass.cpp", + "upstream_sha256": "3a5a73a85cfab31871f38a92862838871bd66954fdce4bbeb46f0596fa7cac26" + }, + "mutex/ctor.copy.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.copy.compile.pass.cpp", + "upstream_sha256": "01abb04d22631f7a62fc0bdbc0a832f342d79ee5c5370b51dfa25a6888a4593a" + }, + "mutex/ctor.default.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.default.pass.cpp", + "upstream_sha256": "e47b2dff8b9071238160bdbbd56f6a2437d1c358c5bedf615080465e367950aa" + }, + "mutex/lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/lock.pass.cpp", + "upstream_sha256": "120e7204cdae0919ff0635bf3acb8839d7a1e7ce63170c5921cf687b841ba7d4" + }, + "mutex/try_lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/try_lock.pass.cpp", + "upstream_sha256": "81044a279dc66a75aa5e8b39f37576dfff3356a439f7eb842f14e9392f9a29cf" + }, + "recursive_mutex/assign.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.pass.cpp", + "upstream_sha256": "f38a4c75e7ee06f637e8f81be81c56c4cd231664c2e7c63782d2b1150311d9e0" + }, + "recursive_mutex/ctor.copy.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.copy.compile.pass.cpp", + "upstream_sha256": "7e28097fb89e9fe17ce5ff61e128d56827c73b8b6aeba8c2b95fd10ad8b28294" + }, + "recursive_mutex/ctor.default.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.default.pass.cpp", + "upstream_sha256": "94230436de215238431d80c16474b019f9b6965350f0ad5429205f4cdb3364f4" + }, + "recursive_mutex/lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/lock.pass.cpp", + "upstream_sha256": "10a631c90893d2ac5a25425b43f7ea70e57c5149ccebc037f02284b10010f747" + }, + "recursive_mutex/try_lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/try_lock.pass.cpp", + "upstream_sha256": "7555b07e2bfc47bffd02fa9bd7d6551da809d340aced09a16bdb0f58959a39b8" + }, + "shared_mutex/assign.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/assign.compile.pass.cpp", + "upstream_sha256": "31c8d543f322febf4609eb0f338799c8359fcc1a7c417822359ba2e9bed7eb84" + }, + "shared_mutex/ctor.copy.compile.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/ctor.copy.compile.pass.cpp", + "upstream_sha256": "a6cf086e60c06f093eb82221a8783a4f5547f49810969b16cecef68b42879546" + }, + "shared_mutex/ctor.default.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/ctor.default.pass.cpp", + "upstream_sha256": "91e57db57a78002d09ddbdbc95e97266033d6f2cb673a37598a0242d03c63d95" + }, + "shared_mutex/lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/lock.pass.cpp", + "upstream_sha256": "cc3c4c25c7cf118f23bf6adca6a4acf68fd18c1c065531a87f6b86256fccd985" + }, + "shared_mutex/lock_shared.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/lock_shared.pass.cpp", + "upstream_sha256": "7dd1b8e27d15d5a78615fae70c3add1843dd389f1368c0a64c268d46acf05cb5" + }, + "shared_mutex/try_lock.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/try_lock.pass.cpp", + "upstream_sha256": "94f90b7d366e10132a09ddf6593239718ca5afd858da9dd3ecd1eeec4439270d" + }, + "shared_mutex/try_lock_shared.pass.cpp": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/try_lock_shared.pass.cpp", + "upstream_sha256": "0d287f284210a11dba4d5702c8301ebd92aa057320c390c46a8f051611816a9e" + }, + "support/test_macros.h": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/support/test_macros.h", + "upstream_sha256": "ed5fe25dd7d279a5411905dfcddf21aa207d8c39a2c6672de872b270676ba012" + }, + "support/make_test_thread.h": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/test/support/make_test_thread.h", + "upstream_sha256": "5616253d10fa1408faf49a04b5607ba4fd648381522315b5f671690741d3c0f7" + }, + "LICENSE.TXT": { + "upstream_url": "https://github.com/llvm/llvm-project/blob/ca7933e47d3a3451d81e72ac174dcb5aa28b59d1/libcxx/LICENSE.TXT", + "upstream_sha256": "539dd7aed86e8a4f12cbdd0e6c50c189c7d74847e4fecc64ce2c6ee3a01da38b" + } + } +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp new file mode 100644 index 00000000..beb70c2b --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class mutex; + +// mutex& operator=(const mutex&) = delete; + +#include "../../custom_mutex.hpp" + +#include + +static_assert(!std::is_copy_assignable::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp new file mode 100644 index 00000000..8873a313 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class mutex; + +// mutex(const mutex&) = delete; + +#include "../../custom_mutex.hpp" + +#include + +static_assert(!std::is_copy_constructible::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp new file mode 100644 index 00000000..b8f50d72 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class mutex; + +// mutex() noexcept; + +#include "../../custom_mutex.hpp" + +#include +#include + +static_assert(std::is_nothrow_default_constructible::value, ""); + +int main(int, char**) { + // The mutex is unlocked after default construction + { + MyMutex m; + assert(m.try_lock()); + m.unlock(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp new file mode 100644 index 00000000..2daed7be --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 +// UNSUPPORTED: no-threads + +// + +// class mutex; + +// void lock(); + +#include "../../custom_mutex.hpp" + +#include +#include +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + MyMutex m; + m.lock(); + m.unlock(); + } + + // Lock a mutex that is already locked. This should block until it is unlocked. + { + std::atomic ready(false); + MyMutex m; + m.lock(); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; + m.unlock(); + + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + MyMutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp new file mode 100644 index 00000000..17fd2f62 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 +// UNSUPPORTED: no-threads + +// + +// class mutex; + +// bool try_lock(); + +#include "../../custom_mutex.hpp" + +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + MyMutex m; + bool succeeded = m.try_lock(); + assert(succeeded); + m.unlock(); + } + + // Try to lock a mutex that is already locked. This should fail. + { + MyMutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); + t.join(); + + m.unlock(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp new file mode 100644 index 00000000..fec3c2a0 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class recursive_mutex; + +// recursive_mutex& operator=(const recursive_mutex&) = delete; + +#include "../../custom_recursive_mutex.hpp" + +#include + +static_assert(!std::is_copy_assignable::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp new file mode 100644 index 00000000..60066837 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class recursive_mutex; + +// recursive_mutex(const recursive_mutex&) = delete; + +#include "../../custom_recursive_mutex.hpp" + +#include + +static_assert(!std::is_copy_constructible::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp new file mode 100644 index 00000000..bccc6a1d --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads + +// + +// class recursive_mutex; + +// recursive_mutex(); + +#include "../../custom_recursive_mutex.hpp" + +#include + +int main(int, char**) { + // The mutex is unlocked after default construction + { + MyRecursiveMutex m; + assert(m.try_lock()); + m.unlock(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp new file mode 100644 index 00000000..b66c61c0 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp @@ -0,0 +1,111 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 +// UNSUPPORTED: no-threads + +// + +// class recursive_mutex; + +// void lock(); + +#include "../../custom_recursive_mutex.hpp" + +#include +#include +#include +#include + +#include "make_test_thread.h" + +bool is_lockable(MyRecursiveMutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; +} + +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + MyRecursiveMutex m; + m.lock(); + m.unlock(); + } + + // Lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + MyRecursiveMutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + m.lock(); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } + + // Lock a mutex that is already locked by another thread. This should block until it is unlocked. + { + std::atomic ready(false); + MyRecursiveMutex m; + m.lock(); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; + m.unlock(); + + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + MyRecursiveMutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp new file mode 100644 index 00000000..8319852c --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 +// UNSUPPORTED: no-threads + +// + +// class recursive_mutex; + +// bool try_lock(); + +#include "../../custom_recursive_mutex.hpp" + +#include +#include + +#include "make_test_thread.h" + +bool is_lockable(MyRecursiveMutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; +} + +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + MyRecursiveMutex m; + bool succeeded = m.try_lock(); + assert(succeeded); + m.unlock(); + } + + // Try to lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + MyRecursiveMutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + assert(m.try_lock()); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } + + // Try to lock a mutex that is already locked by another thread. This should fail. + { + MyRecursiveMutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); + t.join(); + + m.unlock(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py new file mode 100644 index 00000000..4adf8da7 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/run.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Compile and run the libc++ tests adapted to the custom mutex classes.""" + +import argparse +import os +from pathlib import Path +import shlex +import subprocess +import tempfile + + +ROOT = Path(__file__).resolve().parent + + +def run_step(command, timeout): + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=timeout) + except (OSError, subprocess.TimeoutExpired) as error: + print(f" {shlex.join(command)}\n {error}", flush=True) + return False + if result.returncode: + print(f" {shlex.join(command)}\n{result.stdout}{result.stderr}", flush=True) + return False + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--cxx", default=os.environ.get("CXX", "clang++"), + help="compiler executable (default: CXX or clang++)") + parser.add_argument("--stdlib", choices=("libstdc++", "libc++", "default"), + default="libstdc++", + help="standard library; use default with GCC") + parser.add_argument("--filter", default="", metavar="TEXT", + help="run only tests whose relative path contains TEXT") + args = parser.parse_args() + + tests = sorted(path for path in ROOT.glob("*/*.cpp") + if args.filter in path.relative_to(ROOT).as_posix()) + if not tests: + parser.error("no tests match the filter") + + flags = ["-std=c++17", "-pthread", "-g", "-O1", "-UNDEBUG", + "-I" + str(ROOT / "support")] + if args.stdlib != "default": + flags.append("-stdlib=" + args.stdlib) + + passed = 0 + with tempfile.TemporaryDirectory(prefix="brick-mutex-tests-") as build_dir: + for index, source in enumerate(tests): + name = source.relative_to(ROOT).as_posix() + command = [args.cxx, *flags, str(source)] + compile_only = source.name.endswith(".compile.pass.cpp") + executable = Path(build_dir) / str(index) + command += ["-fsyntax-only"] if compile_only else ["-o", str(executable)] + if not run_step(command, timeout=60): + print(f"FAIL (compile) {name}", flush=True) + continue + if not compile_only and not run_step([str(executable)], timeout=20): + print(f"FAIL (run) {name}", flush=True) + continue + passed += 1 + print(f"PASS {name}", flush=True) + + print(f"{passed}/{len(tests)} passed ({args.cxx}, {args.stdlib})", flush=True) + return 0 if passed == len(tests) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp new file mode 100644 index 00000000..0d31011d --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// shared_mutex& operator=(const shared_mutex&) = delete; + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include + +static_assert(!std::is_copy_assignable::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp new file mode 100644 index 00000000..f66cb9cb --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// shared_mutex(const shared_mutex&) = delete; + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include + +static_assert(!std::is_copy_constructible::value, ""); diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp new file mode 100644 index 00000000..4b09b3d6 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// shared_mutex(); + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +int main(int, char**) { + MySharedMutex m; + (void)m; + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp new file mode 100644 index 00000000..53dfe5b3 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp @@ -0,0 +1,108 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// void lock(); + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include +#include +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Exclusive-lock a mutex that is not locked yet. This should succeed. + { + MySharedMutex m; + m.lock(); + m.unlock(); + } + + // Exclusive-lock a mutex that is already locked exclusively. This should block until it is unlocked. + { + std::atomic ready(false); + MySharedMutex m; + m.lock(); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for the thread + // to take the lock while we're still holding it and for the test to still pass. + is_locked_from_main = false; + m.unlock(); + + t.join(); + } + + // Exclusive-lock a mutex that is already share-locked. This should block until it is unlocked. + { + std::atomic ready(false); + MySharedMutex m; + m.lock_shared(); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while we're still holding it and for the test to still pass. + is_locked_from_main = false; + m.unlock_shared(); + + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter = 0; + MySharedMutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp new file mode 100644 index 00000000..009ec6a6 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp @@ -0,0 +1,145 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// void lock_shared(); + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Lock-shared a mutex that is not locked yet. This should succeed. + { + MySharedMutex m; + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + m.lock_shared(); + m.unlock_shared(); + })); + } + + for (auto& t : threads) + t.join(); + } + + // Lock-shared a mutex that is already exclusively locked. This should block until it is unlocked. + { + std::atomic ready(0); + MySharedMutex m; + m.lock(); + std::atomic is_locked_from_main(true); + + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + ++ready; + while (ready < 5) + /* wait until all threads have been created */; + + m.lock_shared(); + assert(!is_locked_from_main); + m.unlock_shared(); + })); + } + + while (ready < 5) + /* wait until all threads have been created */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while we're still holding it and for the test to still pass. + is_locked_from_main = false; + m.unlock(); + + for (auto& t : threads) + t.join(); + } + + // Lock-shared a mutex that is already lock-shared. This should succeed. + { + std::atomic ready(0); + MySharedMutex m; + m.lock_shared(); + + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + ++ready; + while (ready < 5) + /* wait until all threads have been created */; + + m.lock_shared(); + m.unlock_shared(); + })); + } + + while (ready < 5) + /* wait until all threads have been created */; + + m.unlock_shared(); + + for (auto& t : threads) + t.join(); + } + + // Create several threads that all acquire-shared the same mutex and make sure that each + // thread successfully acquires-shared the mutex. + // + // We record how many other threads were holding the mutex when it was acquired, which allows + // us to know whether the test was somewhat effective at causing multiple threads to lock at + // the same time. + { + MySharedMutex mutex; + std::vector threads; + constexpr int n_threads = 5; + std::atomic holders = 0; + int concurrent_holders[n_threads] = {}; + std::atomic ready = false; + + for (int i = 0; i != n_threads; ++i) { + threads.push_back(support::make_test_thread([&, i] { + while (!ready) { + // spin + } + + mutex.lock_shared(); + ++holders; + concurrent_holders[i] = holders; + + mutex.unlock_shared(); + --holders; + })); + } + + ready = true; // let the threads actually start shared-acquiring the mutex + for (auto& t : threads) + t.join(); + + // We can't guarantee that we'll ever have more than 1 concurrent holder so that's what + // we assert, however in principle we should often trigger more than 1 concurrent holder. + int max_concurrent_holders = *std::max_element(std::begin(concurrent_holders), std::end(concurrent_holders)); + assert(max_concurrent_holders >= 1); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp new file mode 100644 index 00000000..ec853353 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// bool try_lock(); + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Try to exclusive-lock a mutex that is not locked yet. This should succeed. + { + MySharedMutex m; + bool succeeded = m.try_lock(); + assert(succeeded); + m.unlock(); + } + + // Try to exclusive-lock a mutex that is already locked exclusively. This should fail. + { + MySharedMutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + bool succeeded = m.try_lock(); + assert(!succeeded); + }); + t.join(); + + m.unlock(); + } + + // Try to exclusive-lock a mutex that is already share-locked. This should fail. + { + MySharedMutex m; + m.lock_shared(); + + std::thread t = support::make_test_thread([&] { + bool succeeded = m.try_lock(); + assert(!succeeded); + }); + t.join(); + + m.unlock_shared(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp new file mode 100644 index 00000000..1ba9abb5 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14 + +// + +// class shared_mutex; + +// bool try_lock_shared(); + +#include "../../../shared_mutex/custom_shared_mutex.hpp" + +#include +#include +#include + +#include "make_test_thread.h" + +int main(int, char**) { + // Try to lock-shared a mutex that is not locked yet. This should succeed. + { + MySharedMutex m; + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + bool succeeded = m.try_lock_shared(); + assert(succeeded); + m.unlock_shared(); + })); + } + + for (auto& t : threads) + t.join(); + } + + // Try to lock-shared a mutex that is already exclusively locked. This should fail. + { + MySharedMutex m; + m.lock(); + + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + bool succeeded = m.try_lock_shared(); + assert(!succeeded); + })); + } + + for (auto& t : threads) + t.join(); + + m.unlock(); + } + + // Try to lock-shared a mutex that is already lock-shared. This should succeed. + { + MySharedMutex m; + m.lock_shared(); + std::vector threads; + for (int i = 0; i != 5; ++i) { + threads.push_back(support::make_test_thread([&] { + bool succeeded = m.try_lock_shared(); + assert(succeeded); + m.unlock_shared(); + })); + } + m.unlock_shared(); + + for (auto& t : threads) + t.join(); + } + + return 0; +} diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/make_test_thread.h b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/make_test_thread.h new file mode 100644 index 00000000..7a4e04b4 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/make_test_thread.h @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_SUPPORT_MAKE_TEST_THREAD_H +#define TEST_SUPPORT_MAKE_TEST_THREAD_H + +#include +#include + +#include "test_macros.h" + +namespace support { + +// These functions are used to mock the creation of threads within the test suite. +// +// This provides a vendor-friendly way of making the test suite work even on platforms +// where the standard thread constructors don't work (e.g. embedded environments where +// creating a thread requires additional information like setting attributes). +// +// Vendors can keep a downstream diff in this file to create threads however they +// need on their platform, and the majority of the test suite will work out of the +// box. Of course, tests that exercise the standard thread constructors won't work, +// but any other test that only creates threads as a side effect of testing should +// work if they use the utilities in this file. + +template +std::thread make_test_thread(F&& f, Args&&... args) { + return std::thread(std::forward(f), std::forward(args)...); +} + +#if TEST_STD_VER >= 20 +template +std::jthread make_test_jthread(F&& f, Args&&... args) { + return std::jthread(std::forward(f), std::forward(args)...); +} +#endif + +} // namespace support + +#endif // TEST_SUPPORT_MAKE_TEST_THREAD_H diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/test_macros.h b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/test_macros.h new file mode 100644 index 00000000..8d88d6fa --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/support/test_macros.h @@ -0,0 +1,550 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef SUPPORT_TEST_MACROS_HPP +#define SUPPORT_TEST_MACROS_HPP + +#ifdef __has_include +# if __has_include() +# include +# else +# include +# endif +#else +# include +#endif + +#define TEST_STRINGIZE_IMPL(...) #__VA_ARGS__ +#define TEST_STRINGIZE(...) TEST_STRINGIZE_IMPL(__VA_ARGS__) + +#define TEST_CONCAT1(X, Y) X##Y +#define TEST_CONCAT(X, Y) TEST_CONCAT1(X, Y) + +#ifdef __has_feature +#define TEST_HAS_FEATURE(X) __has_feature(X) +#else +#define TEST_HAS_FEATURE(X) 0 +#endif + +#ifndef __has_include +#define __has_include(...) 0 +#endif + +#ifdef __has_extension +#define TEST_HAS_EXTENSION(X) __has_extension(X) +#else +#define TEST_HAS_EXTENSION(X) 0 +#endif + +#ifdef __has_warning +#define TEST_HAS_WARNING(X) __has_warning(X) +#else +#define TEST_HAS_WARNING(X) 0 +#endif + +#ifdef __has_builtin +#define TEST_HAS_BUILTIN(X) __has_builtin(X) +#else +#define TEST_HAS_BUILTIN(X) 0 +#endif +#ifdef __is_identifier +// '__is_identifier' returns '0' if '__x' is a reserved identifier provided by +// the compiler and '1' otherwise. +#define TEST_HAS_BUILTIN_IDENTIFIER(X) !__is_identifier(X) +#else +#define TEST_HAS_BUILTIN_IDENTIFIER(X) 0 +#endif + +#if defined(__EDG__) +# define TEST_COMPILER_EDG +#elif defined(__clang__) +# define TEST_COMPILER_CLANG +# if defined(__apple_build_version__) +# define TEST_COMPILER_APPLE_CLANG +# endif +#elif defined(_MSC_VER) +# define TEST_COMPILER_MSVC +#elif defined(__GNUC__) +# define TEST_COMPILER_GCC +#endif + +#if defined(__apple_build_version__) +// Given AppleClang XX.Y.Z, TEST_APPLE_CLANG_VER is XXYZ (e.g. AppleClang 14.0.3 => 1403) +#define TEST_APPLE_CLANG_VER (__apple_build_version__ / 10000) +#elif defined(__clang_major__) +#define TEST_CLANG_VER (__clang_major__ * 100) + __clang_minor__ +#elif defined(__GNUC__) +// Given GCC XX.YY.ZZ, TEST_GCC_VER is XXYYZZ +#define TEST_GCC_VER ((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100) + __GNUC_PATCHLEVEL__) +#endif + +/* Make a nice name for the standard version */ +#ifndef TEST_STD_VER +#if __cplusplus <= 199711L +# define TEST_STD_VER 3 +#elif __cplusplus <= 201103L +# define TEST_STD_VER 11 +#elif __cplusplus <= 201402L +# define TEST_STD_VER 14 +#elif __cplusplus <= 201703L +# define TEST_STD_VER 17 +#elif __cplusplus <= 202002L +# define TEST_STD_VER 20 +#elif __cplusplus <= 202302L +# define TEST_STD_VER 23 +#else +# define TEST_STD_VER 99 // greater than current standard +// This is deliberately different than _LIBCPP_STD_VER to discourage matching them up. +#endif +#endif + +// Attempt to deduce the GLIBC version +#if (defined(__has_include) && __has_include()) || \ + defined(__linux__) +#include +#if defined(__GLIBC_PREREQ) +#define TEST_HAS_GLIBC +#define TEST_GLIBC_PREREQ(major, minor) __GLIBC_PREREQ(major, minor) +#endif +#endif + +#if TEST_STD_VER >= 11 +# define TEST_ALIGNOF(...) alignof(__VA_ARGS__) +# define TEST_ALIGNAS(...) alignas(__VA_ARGS__) +# define TEST_CONSTEXPR constexpr +# define TEST_NOEXCEPT noexcept +# define TEST_NOEXCEPT_FALSE noexcept(false) +# define TEST_NOEXCEPT_COND(...) noexcept(__VA_ARGS__) +#else +# if defined(TEST_COMPILER_CLANG) +# define TEST_ALIGNOF(...) _Alignof(__VA_ARGS__) +# else +# define TEST_ALIGNOF(...) __alignof(__VA_ARGS__) +# endif +# define TEST_ALIGNAS(...) __attribute__((__aligned__(__VA_ARGS__))) +# define TEST_CONSTEXPR +# define TEST_NOEXCEPT throw() +# define TEST_NOEXCEPT_FALSE +# define TEST_NOEXCEPT_COND(...) +#endif + +#if TEST_STD_VER >= 11 +# define TEST_THROW_SPEC(...) +#else +# define TEST_THROW_SPEC(...) throw(__VA_ARGS__) +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L +# define TEST_IS_CONSTANT_EVALUATED std::is_constant_evaluated() +#elif TEST_HAS_BUILTIN(__builtin_is_constant_evaluated) +# define TEST_IS_CONSTANT_EVALUATED __builtin_is_constant_evaluated() +#else +# define TEST_IS_CONSTANT_EVALUATED false +#endif + +#if TEST_STD_VER >= 20 +# define TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED true +#else +# define TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED (!TEST_IS_CONSTANT_EVALUATED) +#endif + +#if TEST_STD_VER >= 23 +# define TEST_STD_AT_LEAST_23_OR_RUNTIME_EVALUATED true +#else +# define TEST_STD_AT_LEAST_23_OR_RUNTIME_EVALUATED (!TEST_IS_CONSTANT_EVALUATED) +#endif + +#if TEST_STD_VER >= 26 +# define TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED true +#else +# define TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED (!TEST_IS_CONSTANT_EVALUATED) +#endif + +#if TEST_STD_VER >= 14 +# define TEST_CONSTEXPR_CXX14 constexpr +#else +# define TEST_CONSTEXPR_CXX14 +#endif + +#if TEST_STD_VER >= 17 +# define TEST_CONSTEXPR_CXX17 constexpr +#else +# define TEST_CONSTEXPR_CXX17 +#endif + +#if TEST_STD_VER >= 20 +# define TEST_CONSTEXPR_CXX20 constexpr +#else +# define TEST_CONSTEXPR_CXX20 +#endif + +#if TEST_STD_VER >= 23 +# define TEST_CONSTEXPR_CXX23 constexpr +#else +# define TEST_CONSTEXPR_CXX23 +#endif + +#if TEST_STD_VER >= 26 +# define TEST_CONSTEXPR_CXX26 constexpr +#else +# define TEST_CONSTEXPR_CXX26 +#endif + +#define TEST_ALIGNAS_TYPE(...) TEST_ALIGNAS(TEST_ALIGNOF(__VA_ARGS__)) + +#if !TEST_HAS_FEATURE(cxx_rtti) && !defined(__cpp_rtti) \ + && !defined(__GXX_RTTI) +#define TEST_HAS_NO_RTTI +#endif + +#if !defined(TEST_HAS_NO_RTTI) +# define RTTI_ASSERT(X) assert(X) +#else +# define RTTI_ASSERT(X) +#endif + +#if !TEST_HAS_FEATURE(cxx_exceptions) && !defined(__cpp_exceptions) \ + && !defined(__EXCEPTIONS) +#define TEST_HAS_NO_EXCEPTIONS +#endif + +#if TEST_HAS_FEATURE(address_sanitizer) || TEST_HAS_FEATURE(hwaddress_sanitizer) || \ + TEST_HAS_FEATURE(memory_sanitizer) || TEST_HAS_FEATURE(thread_sanitizer) +#define TEST_HAS_SANITIZERS +#define TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +# ifdef _LIBCPP_HAS_NO_ALIGNED_ALLOCATION +# define TEST_HAS_NO_ALIGNED_ALLOCATION +# endif +#elif defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_ALIGNED_ALLOCATION +# define TEST_HAS_NO_ALIGNED_ALLOCATION +#elif TEST_STD_VER < 17 && (!defined(__cpp_aligned_new) || __cpp_aligned_new < 201606L) +# define TEST_HAS_NO_ALIGNED_ALLOCATION +#endif + +#if TEST_STD_VER > 17 +# define TEST_CONSTINIT constinit +#elif __has_cpp_attribute(clang::require_constant_initialization) +# define TEST_CONSTINIT [[clang::require_constant_initialization]] +#else +# define TEST_CONSTINIT +#endif + +#if TEST_STD_VER < 11 +#define ASSERT_NOEXCEPT(...) +#define ASSERT_NOT_NOEXCEPT(...) +#else +#define ASSERT_NOEXCEPT(...) \ + static_assert(noexcept(__VA_ARGS__), "Operation must be noexcept") + +#define ASSERT_NOT_NOEXCEPT(...) \ + static_assert(!noexcept(__VA_ARGS__), "Operation must NOT be noexcept") +#endif + +/* Macros for testing libc++ specific behavior and extensions */ +#if defined(_LIBCPP_VERSION) +#define LIBCPP_ASSERT(...) assert(__VA_ARGS__) +#define LIBCPP_STATIC_ASSERT(...) static_assert(__VA_ARGS__) +#define LIBCPP_ASSERT_NOEXCEPT(...) ASSERT_NOEXCEPT(__VA_ARGS__) +#define LIBCPP_ASSERT_NOT_NOEXCEPT(...) ASSERT_NOT_NOEXCEPT(__VA_ARGS__) +#define LIBCPP_ONLY(...) __VA_ARGS__ +#else +#define LIBCPP_ASSERT(...) static_assert(true, "") +#define LIBCPP_STATIC_ASSERT(...) static_assert(true, "") +#define LIBCPP_ASSERT_NOEXCEPT(...) static_assert(true, "") +#define LIBCPP_ASSERT_NOT_NOEXCEPT(...) static_assert(true, "") +#define LIBCPP_ONLY(...) static_assert(true, "") +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +# define LIBCPP_NON_FROZEN_ASSERT(...) static_assert(true, "") +#else +# define LIBCPP_NON_FROZEN_ASSERT(...) LIBCPP_ASSERT(__VA_ARGS__) +#endif + +#if __has_cpp_attribute(nodiscard) +# define TEST_NODISCARD [[nodiscard]] +#else +# define TEST_NODISCARD +#endif + +#define TEST_IGNORE_NODISCARD (void) + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +// from-chars is a C++17 feature, so it's never available anyways +#elif !defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT +# define TEST_HAS_FROM_CHARS_FLOATING_POINT +#endif + +namespace test_macros_detail { +template +struct is_same { enum { value = 0};} ; +template +struct is_same { enum {value = 1}; }; +} // namespace test_macros_detail + +#define ASSERT_SAME_TYPE(...) \ + static_assert((test_macros_detail::is_same<__VA_ARGS__>::value), \ + "Types differ unexpectedly") + +#ifndef TEST_HAS_NO_EXCEPTIONS +#define TEST_THROW(...) throw __VA_ARGS__ +#else +#if defined(__GNUC__) +#define TEST_THROW(...) __builtin_abort() +#else +#include +#define TEST_THROW(...) ::abort() +#endif +#endif + +#if defined(__GNUC__) || defined(__clang__) +// This function can be used to hide some objects from compiler optimizations. +// +// For example, this is useful to hide the result of a call to `new` and ensure +// that the compiler doesn't elide the call to new/delete. Otherwise, elliding +// calls to new/delete is allowed by the Standard and compilers actually do it +// when optimizations are enabled. +template +inline Tp const& DoNotOptimize(Tp const& value) { + // The `m` constraint is invalid in the AMDGPU backend. +# if defined(__AMDGPU__) || defined(__NVPTX__) + asm volatile("" : : "r"(value) : "memory"); +# else + asm volatile("" : : "r,m"(value) : "memory"); +# endif + return value; +} + +template +inline Tp& DoNotOptimize(Tp& value) { + // The `m` and `r` output constraint is invalid in the AMDGPU backend as well + // as i8 / i1 arguments, so we just capture the pointer instead. +# if defined(__AMDGPU__) + Tp* tmp = &value; + asm volatile("" : "+v"(tmp) : : "memory"); +# elif defined(__clang__) + asm volatile("" : "+r,m"(value) : : "memory"); +# else + asm volatile("" : "+m,r"(value) : : "memory"); +# endif + return value; +} +#else +#include +template +inline Tp const& DoNotOptimize(Tp const& value) { + const volatile void* volatile unused = __builtin_addressof(value); + static_cast(unused); + _ReadWriteBarrier(); + return value; +} +#endif + +#if defined(__GNUC__) +#define TEST_ALWAYS_INLINE __attribute__((always_inline)) +#define TEST_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) +#define TEST_ALWAYS_INLINE __forceinline +#define TEST_NOINLINE __declspec(noinline) +#else +#define TEST_ALWAYS_INLINE +#define TEST_NOINLINE +#endif + +#ifdef _WIN32 +#define TEST_NOT_WIN32(...) ((void)0) +#else +#define TEST_NOT_WIN32(...) __VA_ARGS__ +#endif + +#if defined(TEST_WINDOWS_DLL) ||defined(__MVS__) || defined(_AIX) +// Macros for waiving cases when we can't count allocations done within +// the library implementation. +// +// On Windows, when libc++ is built as a DLL, references to operator new/delete +// within the DLL are bound at link time to the operator new/delete within +// the library; replacing them in the user executable doesn't override the +// calls within the library. +// +// The same goes on IBM zOS. +// The same goes on AIX. +#define ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(...) ((void)(__VA_ARGS__)) +#define TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 0 +#else +#define ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(...) assert(__VA_ARGS__) +#define TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 1 +#endif + +#if (defined(TEST_WINDOWS_DLL) && !defined(_MSC_VER)) || \ + defined(__MVS__) +// Normally, a replaced e.g. 'operator new' ends up used if the user code +// does a call to e.g. 'operator new[]'; it's enough to replace the base +// versions and have it override all of them. +// +// When the fallback operators are located within the libc++ library and we +// can't override the calls within it (see above), this fallback mechanism +// doesn't work either. +// +// On Windows, when using the MSVC vcruntime, the operator new/delete fallbacks +// are linked separately from the libc++ library, linked statically into +// the end user executable, and these fallbacks work even in DLL configurations. +// In MinGW configurations when built as a DLL, and on zOS, these fallbacks +// don't work though. +#define ASSERT_WITH_OPERATOR_NEW_FALLBACKS(...) ((void)(__VA_ARGS__)) +#else +#define ASSERT_WITH_OPERATOR_NEW_FALLBACKS(...) assert(__VA_ARGS__) +#endif + +#ifdef _WIN32 +#define TEST_WIN_NO_FILESYSTEM_PERMS_NONE +#endif + +// Support for carving out parts of the test suite, like removing wide characters, etc. +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_WIDE_CHARACTERS +# define TEST_HAS_NO_WIDE_CHARACTERS +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_UNICODE +# define TEST_HAS_NO_UNICODE +#elif defined(_MSVC_EXECUTION_CHARACTER_SET) && _MSVC_EXECUTION_CHARACTER_SET != 65001 +# define TEST_HAS_NO_UNICODE +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +# ifdef _LIBCPP_HAS_OPEN_WITH_WCHAR +# define TEST_HAS_OPEN_WITH_WCHAR +# endif +#elif defined(_LIBCPP_VERSION) && _LIBCPP_HAS_OPEN_WITH_WCHAR +# define TEST_HAS_OPEN_WITH_WCHAR +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +# ifdef _LIBCPP_HAS_NO_INT128 +# define TEST_HAS_NO_INT128 +# endif +#elif (defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_INT128) || defined(_MSVC_STL_VERSION) +# define TEST_HAS_NO_INT128 +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_LOCALIZATION +# define TEST_HAS_NO_LOCALIZATION +#endif + +#if TEST_STD_VER <= 17 || !defined(__cpp_char8_t) +# define TEST_HAS_NO_CHAR8_T +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_THREADS +# define TEST_HAS_NO_THREADS +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_FILESYSTEM +# define TEST_HAS_NO_FILESYSTEM +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +# ifdef _LIBCPP_HAS_NO_C8RTOMB_MBRTOC8 +# define TEST_HAS_NO_C8RTOMB_MBRTOC8 +# endif +#elif defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_C8RTOMB_MBRTOC8 +# define TEST_HAS_NO_C8RTOMB_MBRTOC8 +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_RANDOM_DEVICE +# define TEST_HAS_NO_RANDOM_DEVICE +#endif + +#ifdef _LIBCPP_USE_FROZEN_CXX03_HEADERS +// This is a C++20 feature, so it's never available anyways +# define TEST_HAS_NO_EXPERIMENTAL_TZDB +#elif defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_EXPERIMENTAL_TZDB +# define TEST_HAS_NO_EXPERIMENTAL_TZDB +#endif + +#if defined(_LIBCPP_VERSION) && !_LIBCPP_HAS_TIME_ZONE_DATABASE +# define TEST_HAS_NO_TIME_ZONE_DATABASE +#endif + +#if defined(TEST_COMPILER_CLANG) +# define TEST_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") +# define TEST_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +# define TEST_CLANG_DIAGNOSTIC_IGNORED(str) _Pragma(TEST_STRINGIZE(clang diagnostic ignored str)) +# define TEST_GCC_DIAGNOSTIC_IGNORED(str) +# define TEST_MSVC_DIAGNOSTIC_IGNORED(num) +#elif defined(TEST_COMPILER_GCC) +# define TEST_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +# define TEST_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +# define TEST_CLANG_DIAGNOSTIC_IGNORED(str) +# define TEST_GCC_DIAGNOSTIC_IGNORED(str) _Pragma(TEST_STRINGIZE(GCC diagnostic ignored str)) +# define TEST_MSVC_DIAGNOSTIC_IGNORED(num) +#elif defined(TEST_COMPILER_MSVC) +# define TEST_DIAGNOSTIC_PUSH _Pragma("warning(push)") +# define TEST_DIAGNOSTIC_POP _Pragma("warning(pop)") +# define TEST_CLANG_DIAGNOSTIC_IGNORED(str) +# define TEST_GCC_DIAGNOSTIC_IGNORED(str) +# define TEST_MSVC_DIAGNOSTIC_IGNORED(num) _Pragma(TEST_STRINGIZE(warning(disable: num))) +#else +# define TEST_DIAGNOSTIC_PUSH +# define TEST_DIAGNOSTIC_POP +# define TEST_CLANG_DIAGNOSTIC_IGNORED(str) +# define TEST_GCC_DIAGNOSTIC_IGNORED(str) +# define TEST_MSVC_DIAGNOSTIC_IGNORED(num) +#endif + +#if __has_cpp_attribute(msvc::no_unique_address) +#define TEST_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif __has_cpp_attribute(no_unique_address) +#define TEST_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define TEST_NO_UNIQUE_ADDRESS +#endif + +#ifdef _LIBCPP_SHORT_WCHAR +# define TEST_SHORT_WCHAR +#endif + +#ifdef _LIBCPP_ABI_MICROSOFT +# define TEST_ABI_MICROSOFT +#endif + +// This is a temporary workaround for user-defined `operator new` definitions +// not being picked up on Apple platforms in some circumstances. This is under +// investigation and should be short-lived. +#ifdef __APPLE__ +# define TEST_WORKAROUND_BUG_109234844_WEAK __attribute__((weak)) +#else +# define TEST_WORKAROUND_BUG_109234844_WEAK /* nothing */ +#endif + +#ifdef _AIX +# define TEST_IF_AIX(arg_true, arg_false) arg_true +#else +# define TEST_IF_AIX(arg_true, arg_false) arg_false +#endif + +// Placement `operator new`/`operator new[]` are not yet constexpr in C++26 +// when using MS ABI, because they are from . +#if defined(__cpp_lib_constexpr_new) && __cpp_lib_constexpr_new >= 202406L +# define TEST_CONSTEXPR_OPERATOR_NEW constexpr +#else +# define TEST_CONSTEXPR_OPERATOR_NEW +#endif + +#if defined(_MSC_VER) || __SIZEOF_LONG_DOUBLE__ == __SIZEOF_DOUBLE__ +# define TEST_LONG_DOUBLE_IS_DOUBLE +#endif + +#if defined(__LDBL_MANT_DIG__) && __LDBL_MANT_DIG__ == 64 +# define TEST_LONG_DOUBLE_IS_80_BIT +#endif + +#endif // SUPPORT_TEST_MACROS_HPP From ac85203a096525abe39c5f2752dc61b5d97070b4 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 20:35:22 +0000 Subject: [PATCH 103/109] let dune-gen.sh ignore C++ files under rocq-brick-libstdcpp/test/mutex/libcpp_tests --- rocq-brick-libstdcpp/test/dune-gen.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocq-brick-libstdcpp/test/dune-gen.sh b/rocq-brick-libstdcpp/test/dune-gen.sh index afdc9fa2..d9396135 100755 --- a/rocq-brick-libstdcpp/test/dune-gen.sh +++ b/rocq-brick-libstdcpp/test/dune-gen.sh @@ -16,8 +16,8 @@ echo "; DO NOT EDIT: Generated by \"$0${1+ $*}\"" # Temporary: `cpp2v --templates` for only those tests in ./templates. -# Blacklist some files for arch_indep -find . -name '*.[ch]pp' | +# The custom mutex conformance suites are compiled and run natively, not by cpp2v. +find . \( -path './mutex/libcpp_tests' \) -prune -o -name '*.[ch]pp' -print | LC_ALL=C sort | while read i; do ./cpp2v-dune-gen.sh -s -- $i -- -std=c++20 -stdlib=libstdc++ From 1a2b17a6275b01f3e523c61dcbc72d001719e7a3 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Wed, 14 Oct 2026 22:04:10 +0000 Subject: [PATCH 104/109] make custom_mutex_hpp_proof.v more compact --- .../test/mutex/custom_mutex_hpp_proof.v | 107 +++++++++--------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v index 552baa9a..95852bae 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v +++ b/rocq-brick-libstdcpp/test/mutex/custom_mutex_hpp_proof.v @@ -59,52 +59,52 @@ Module CustomMutexPreds <: MUTEX_PREDS. Definition token `{Σ : cpp_logic, !G Σ} (γ : gname) (q : cQp.t) : mpred := own γ.(token_gname) (cQp.frac q : token_cmraR). + Definition not_locked `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} (this : ptr) (γ : gname) (th : thread_idT) (q : cQp.t) : mpred := - token γ q ** + own γ.(token_gname) (cQp.frac q : token_cmraR) ** MutexSets.my_mutexes th (coPset.CoPset $ ↑mutex_inv_namespace γ). - Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} - (γ : state_gname) (b : bool) : mpred := + Definition state `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} + (this : ptr) (γ : gname) (b : bool) : mpred := ∃ th q, owner_tid_auth γ.(owner_gname) (if b then Some (th, q) else None) ** if b then - (* the mutex owner deposits its [my_mutexes] and [token], and take away - owner_tid_frag which records the fraction of [token] that it can take - back from invariant when it releases the lock. *) - MutexSets.my_mutexes th (coPset.CoPset $ ↑γ.(state_namespace)) ** - own γ.(token_gname) (cQp.frac q : token_cmraR) + (* The mutex owner deposits [not_locked] and takes [owner_tid_frag], + which records the [th] and [q] stored in the invariant. *) + not_locked this γ th q else owner_tid_frag γ.(owner_gname) None. #[global] Instance state_WeaklyObjective - `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} γ b : - WeaklyObjective (state γ b). - Proof. rewrite /state. destruct b; apply _. Qed. + `{Σ : cpp_logic, MUTEX_SETS : !mutex_set_G Σ, !G Σ} {σ : genv} this γ b : + WeaklyObjective (state this γ b). + Proof. rewrite /state /not_locked. destruct b; apply _. Qed. Section state_laws. Context `{Σ : cpp_logic}. Context `{MUTEX_SETS : !mutex_set_G Σ}. Context `{!G Σ}. - Lemma alloc_state (N : namespace) (HN : N <> nroot) : + Lemma alloc_state {σ : genv} this gi (N : namespace) (HN : N <> nroot) : ⊢ |==> ∃ γ, [| γ.(state_namespace) = N |] ** - own γ.(token_gname) (1%Qp : token_cmraR) ** state γ false. + token (MkGname γ gi) 1$m ** + state this (MkGname γ gi) false. Proof. iMod (own_alloc (1%Qp : token_cmraR)) as (gt) "T"; first done. iMod (OwnerTid.alloc None) as (go) "[OA OF]". iModIntro. iExists (MkStateGname gt go N HN). iSplit; first done. - rewrite /state /=. iFrame "T". + rewrite /token /state /=. iFrame "T". iExists thread_id_specs.default_thread_id, (1$m)%cQp. iFrame. Qed. Lemma state_lock {σ : genv} this (γ : gname) th (q : cQp.t) : - state γ false ** not_locked this γ th q |-- - (|==> state γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q))). + state this γ false ** not_locked this γ th q |-- + (|==> state this γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q))). Proof. - rewrite /state /not_locked /token /mutex_inv_namespace. - iIntros "[State [T Sets]]". + rewrite /state. + iIntros "[State NL]". iDestruct "State" as (old_th old_q) "[OA OF]". iMod (owner_update γ.(owner_gname) None None (Some (th, q)) with "[$OA $OF]") as "[OA OF]". @@ -112,32 +112,32 @@ Module CustomMutexPreds <: MUTEX_PREDS. Qed. Lemma state_unlock {σ : genv} this (γ : gname) th (q : cQp.t) : - state γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- - (|==> state γ false ** not_locked this γ th q). + state this γ true ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- + (|==> state this γ false ** not_locked this γ th q). Proof. - rewrite /state /not_locked /token /mutex_inv_namespace. + rewrite /state. iIntros "[State OF]". - iDestruct "State" as (owner held_q) "(OA & Sets & T)". + iDestruct "State" as (owner held_q) "[OA NL]". iDestruct (observe_2 [| Some (owner, held_q) = Some (th, q) |] with "OA OF") as %Heq. injection Heq as -> ->. iMod (owner_update γ.(owner_gname) _ _ None with "[$OA $OF]") as "[OA OF]". - iModIntro. iFrame "Sets T". iExists th, q. iFrame. + iModIntro. iFrame "NL". iExists th, q. iFrame. Qed. - Lemma unlocked_owner_tid_frag γ th q : - state γ false ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- False. + Lemma unlocked_owner_tid_frag {σ : genv} this γ th q : + state this γ false ** owner_tid_frag γ.(owner_gname) (Some (th, q)) |-- False. Proof. rewrite /state. iIntros "[State OF]". iDestruct "State" as (old_th old_q) "[_ OF0]". iDestruct (owner_tid_frag_exclusive with "OF0 OF") as %[]. Qed. - Lemma locked_full_token γ : - state γ true ** own γ.(token_gname) (1%Qp : token_cmraR) |-- False. + Lemma locked_full_token {σ : genv} this γ : + state this γ true ** token γ 1$m |-- False. Proof. - rewrite /state. iIntros "[State T]". - iDestruct "State" as (th q) "(_ & _ & Held)". + rewrite /state /not_locked /token. iIntros "[State T]". + iDestruct "State" as (th q) "(_ & Held & _)". iDestruct (own_valid_2 with "T Held") as %Hbad. iPureIntro. exact (Qp.not_add_le_l 1 (cQp.frac q) Hbad). Qed. @@ -209,7 +209,7 @@ Module CustomMutexPreds <: MUTEX_PREDS. ∃ b : bool, this ,, _field "MyMutex::m_lock" |-> atomic.R "int" 1$m (if b then 1 else 0)%Z ** - state γ b ** + state this γ b ** if b then emp else P ** this ,, _field "MyMutex::m_owner" |-> thread_id_specs.thread_idR 1$m thread_id_specs.default_thread_id. @@ -242,9 +242,9 @@ Module CustomMutexPreds <: MUTEX_PREDS. iDestruct "Inv" as (b) "(_ & State & _)". iEval (rewrite /locked) in "Locked". iDestruct "Locked" as "[_ OF]". - iEval (rewrite /state) in "State". + iEval (rewrite /state /not_locked) in "State". destruct b. - - iDestruct "State" as (owner held_q) "(>OA & >Sets & _)". + - iDestruct "State" as (owner held_q) "(>OA & _ & >Sets)". iDestruct (observe_2 [| Some (owner, held_q) = Some (th, qt) |] with "OA OF") as %Heq. injection Heq as -> ->. @@ -301,20 +301,22 @@ Module CustomMutexPreds <: MUTEX_PREDS. iEval (rewrite /R _at_sep _at_as_Rep) in "HR". iDestruct "HR" as "(S & #CI & CO)". iMod (cinv_cancel with "CI CO") as "Inv"; [done..|]. - iMod (alloc_state N HN) as (gs) "(%Hnamespace & Tnew & Stnew)". - iMod (cinv_alloc with "[Inv P Stnew T]") as (gi) "[#CInew COnew]"; last first. - - iModIntro. iExists (MkGname gs gi). - iSplit; first (iPureIntro; exact Hnamespace). - rewrite /R _at_sep _at_as_Rep /token /mutex_inv /=. - iFrame "CInew S COnew Tnew". - - iNext. rewrite /mutex_inv. + iMod (cinv_alloc_cofinite ∅ ⊤ N) as (gi) "(_ & COnew & Alloc)". + iMod (alloc_state this gi N HN) as (gs) "(%Hnamespace & Tnew & Stnew)". + iMod ("Alloc" $! (mutex_inv this (MkGname gs gi) P) + with "[] [Inv P Stnew T]") as "#CInew". + { iPureIntro. apply _. } + { iNext. rewrite /mutex_inv. iDestruct "Inv" as (b) "(L & St & Resources)". destruct b. - + iDestruct (locked_full_token with "[$St $T]") as %[]. - + iDestruct "Resources" as "[_ Owner]". + - iDestruct (locked_full_token with "[$St $T]") as %[]. + - iDestruct "Resources" as "[_ Owner]". iExists false. iFrame "L Stnew P Owner". - iApply (affine with "[St T]"); last iAccu. apply mpred_BiAffine. - - apply _. + iApply (affine with "[St T]"); last iAccu. apply mpred_BiAffine. } + iModIntro. iExists (MkGname gs gi). + iSplit; first (iPureIntro; exact Hnamespace). + rewrite /R _at_sep _at_as_Rep /mutex_inv_namespace /= Hnamespace. + iFrame "CInew S COnew Tnew". Qed. End with_Σ. @@ -604,15 +606,16 @@ Module custom_mutex. wname [P] "P". wname [_ |-> atomic.R _ _ _] "L". wname [_ |-> thread_id_specs.thread_idR _ _] "Owner". - iMod (alloc_state N ltac:(done)) as (gs) "(%Hnamespace & T & State)". - iMod (cinv_alloc with "[L P Owner State]") - as (gi) "[#CI CO]"; last first. - - iModIntro. iExists (MkGname gs gi). - iSplit; first (iPureIntro; exact Hnamespace). - rewrite /IR _at_sep _at_as_Rep /mutex_inv /=. - iFrame "CI". iFrame. - - iNext. iExists false. iFrame. - - apply _. + iMod (cinv_alloc_cofinite ∅ ⊤ N) as (gi) "(_ & CO & Alloc)". + iMod (alloc_state this gi N ltac:(done)) as (gs) "(%Hnamespace & T & State)". + iMod ("Alloc" $! (mutex_inv this (MkGname gs gi) P) + with "[] [L P Owner State]") as "#CI". + { iPureIntro. apply _. } + { iNext. rewrite /mutex_inv. iExists false. iFrame. } + iModIntro. iExists (MkGname gs gi). + iSplit; first (iPureIntro; exact Hnamespace). + rewrite /IR _at_sep _at_as_Rep /mutex_inv_namespace /= Hnamespace. + iFrame "CI". iFrame. Qed. Lemma mymutex_dtor_proof : verify[source] "MyMutex::~MyMutex()". From 6f962c9474f6059090abcc113a9a99c5720731c3 Mon Sep 17 00:00:00 2001 From: rinshankaihou Date: Thu, 15 Oct 2026 07:55:00 +0000 Subject: [PATCH 105/109] template spec and proof for lock_guard dtor --- .../proof/mutex/proof/lock_guard.v | 218 +++++++++++++++++- .../proof/mutex/spec/lock_guard.v | 11 +- 2 files changed, 213 insertions(+), 16 deletions(-) diff --git a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v index d1355188..59d7de28 100644 --- a/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/proof/lock_guard.v @@ -169,8 +169,137 @@ Section with_cpp. End with_cpp. -(* Specialization of [ctor_ok] for , , , - and >, whatever that means. *) +Definition lock_guard_dtor_template : Dtor. +Proof. + let c := eval vm_compute in + (meta__source.(skylabs.lang.cpp.syntax.templates.msymbols) !! + (("std::lock_guard" .<< Atype (Tparam "_Mutex") >>) .:: Ndtor)) in + lazymatch c with + | Some (mcore.Template _ (Odestructor ?dtor)) => exact dtor + end. +Defined. + +Definition lock_guard_dtor_subst (mutex : name) : subst.subst.t := + (∅ &: subst.subst._type_param .= {["_Mutex"%pstring := Tnamed mutex]} + &: subst.subst._resolved_member_type .= + {[(Tnamed mutex, Nlocal (Nid "unlock")) := + Tnamed (mutex .:: Nfunction function_qualifiers.N "unlock" [])]} + &: subst.subst._resolved_call .= + {[(mutex .:: Nfunction function_qualifiers.N "unlock" [], []) := + fun es => Emember_call false + (inl (mutex .:: Nfunction function_qualifiers.N "unlock" [], + Direct, "void()"%cpp_type)) + (Emember true (Ethis (Tptr (Tnamed (lock_guard_name mutex)))) + (Nid "_M_device") false (Tref (Tnamed mutex))) es]})%lens. + +(* Resolve the destructor's implicit noexcept after instantiation. *) +Definition lock_guard_dtor (mutex : name) : Dtor. +Proof. + let rec extract result := + let result := eval cbv -[compare.name_eq_dec compare.type_eq_dec] in result in + lazymatch result with + | inr ?dtor => + exact (Build_Dtor dtor.(d_class) dtor.(d_cc) exception_spec.NoThrow dtor.(d_body)) + | context C [compare.name_eq_dec ?n ?n] => + let result := context C [@left (n = n) (n ≠ n) eq_refl] in extract result + | context C [compare.type_eq_dec ?ty ?ty] => + let result := context C [@left (ty = ty) (ty ≠ ty) eq_refl] in extract result + end in + extract (subst_ap.subst.run2 + (subst_ap.subst.M2.subst_dtor lock_guard_dtor_template) + (lock_guard_dtor_subst mutex)). +Defined. + +Lemma lock_guard_dtor_std_mutex_ast : + source.(symbols) !! "std::lock_guard::~lock_guard()"%cpp_name = + Some (Odestructor (lock_guard_dtor "std::mutex")). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_dtor_std_recursive_mutex_ast : + source.(symbols) !! "std::lock_guard::~lock_guard()"%cpp_name = + Some (Odestructor (lock_guard_dtor "std::recursive_mutex"%cpp_name)). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_dtor_std_shared_mutex_ast : + source.(symbols) !! "std::lock_guard::~lock_guard()"%cpp_name = + Some (Odestructor (lock_guard_dtor "std::shared_mutex"%cpp_name)). +Proof. vm_compute. reflexivity. Qed. + +Lemma lock_guard_dtor_std_unique_lock_mutex_ast : + source.(symbols) !! "std::lock_guard>::~lock_guard()"%cpp_name = + Some (Odestructor (lock_guard_dtor "std::unique_lock"%cpp_name)). +Proof. vm_compute. reflexivity. Qed. + +Section with_generic_dtor. + Context `{Σ : cpp_logic, σ : genv}. + Context (tu : translation_unit) (mutex : name) (sz al : N). + Context {mutexT : Type} (mutexR : cQp.t -> mutexT -> Rep). + Context `{!BasicLockable (Tnamed mutex) mutexR}. + Definition inner_unlock_spec : mpred := + specify.exact.method (mutex .:: Nfunction function_qualifiers.N "unlock" []) + function_qualifiers.N Tvoid [] (unlock_basic_lockable (Tnamed mutex) mutexR). + #[local] Hint Opaque do_unlock : sl_opacity. + #[local] Hint Opaque inner_unlock_spec : sl_opacity. + Hypothesis Hstruct : tu.(types) !! lock_guard_name mutex = + Some (Gstruct (lock_guard_struct mutex sz al)). + Hypothesis Hcode : + genv_tu.find_code (lock_guard_name mutex .:: Ndtor) tu = + Some (genv_tu.CTdtor (inl (lock_guard_struct mutex sz al)) (lock_guard_dtor mutex)). + (* Verification of lock_guard.dtor_spec against the template code (Hcode). *) + Lemma dtor_template_ok : + denoteModule tu ** inner_unlock_spec |-- + lock_guard.dtor_spec (mutexR:=mutexR) (Tnamed mutex) tu. + Proof using Hcode Hstruct. + have Hfuel : exists n, NM.cardinal (tu.(types)) = S n. + { rewrite NM.cardinal_1. + have Hin := NM.elements_1 (NM.find_2 Hstruct). + destruct (NM.elements tu.(types)) eqn:Helems. + - inversion Hin. + - eexists. reflexivity. } + destruct Hfuel as [fuel Hfuel]. + match goal with |- _ |-- ?SP => + unshelve epose proof (tactics.VerifySpec.verify_spec_hint_obligation_1 + false false tu σ _ _ Σ _ _ + (genv_tu.CTdtor (inl (lock_guard_struct mutex sz al)) (lock_guard_dtor mutex)) + SP ltac:(unfold lock_guard.dtor_spec, specify.template.dtor, + specify.exact.dtor; apply find_spec.unmaterialized_isspec) ltac:( + unfold mvcg.find_code_of_type; rewrite Hcode; + rewrite bool_decide_true; [constructor | reflexivity])) as Hverify + end. + iIntros "[#Htu #Hunlock]". + iDestruct (Hverify with "Htu") as "(%Hmodels & Hverify)". + iApply "Hverify". + iClear "Hverify". + clear Hverify. + unfold lock_guard_dtor, lock_guard_struct, lock_guard_name. + unfold inner_unlock_spec, specify.exact.method, unlock_basic_lockable. + go. + rewrite wp_minvoke_O.unlock /wp_minvoke /wp_mfptr. + cbn. + go. + iApply (invoke.use_cptrR_method tu _ (Tnamed mutex) CC_C mutex QM + Tvoid Ar_Definite [] _ _ "void()"%cpp_type (mutex_ptr m) [] _ eq_refl with "Hunlock"). + go. + iExists ((mutex_q m)$m)%cQp, (mutex_m m), K. + rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). + repeat lazymatch goal with + | |- environments.envs_entails _ (wp_revert_identity _ _ _ _) => fail + | _ => step + end. + PostCondition.hide_post. + iApply (identity.revert_identity_struct' tu (lock_guard_name mutex) _ this Hmodels + (identity.Rec []) (identity.Rec []) emp emp + ltac:(cbn zeta; rewrite Hfuel; cbn [identity.derivations]; + rewrite Hstruct; constructor) + (identity.red_result _) (identity.red_result _)). + iSplit; first done. + iIntros "_". + go. + Qed. +End with_generic_dtor. + +(* Specialization of [ctor_ok] and [dtor_template_ok] for , + , , and >, whatever that means. *) Section with_std_mutex. Context `{Σ : cpp_logic, σ : genv}. @@ -193,22 +322,24 @@ Section with_std_mutex. vm_compute. reflexivity. Qed. + Lemma dtor_std_mutex_ok : + denoteModule source ** mutex.unlock_spec |-- + lock_guard.dtor_spec "std::mutex" source. + Proof. + apply (dtor_template_ok source "std::mutex" 8 8 + (fun q gP => mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_dtor_std_mutex_ast. + vm_compute. reflexivity. + Qed. + #[global] Instance UNSAFE_R_learnable : forall {HAS_THREADS : HasStdThreads Σ} {σ : genv}, Cbn (Learn (learn_eq ==> learn_eq ==> learn_eq ==> fin_at) mutex.R). Proof. solve_learnable. Qed. #[local] Instance: `{SplitRecord (M T)} := {}. - Lemma dtor_ok : verify[source] "std::lock_guard::~lock_guard()". - Proof. - verify_spec. - rewrite !R.unlock. - go. - iExists K, t. - go with br_erefl. - by rewrite cQp.scale_mut (right_id_L 1%Qp Qp.mul). - Qed. - End with_std_mutex. Section with_std_recursive_mutex. @@ -228,6 +359,18 @@ Section with_std_recursive_mutex. rewrite lock_guard_ctor_std_recursive_mutex_ast. vm_compute. reflexivity. Qed. + + Lemma dtor_std_recursive_mutex_ok : + denoteModule source ** std_recursive_mutex.std_unlock_spec_alt |-- + lock_guard.dtor_spec "std::recursive_mutex" source. + Proof. + apply (dtor_template_ok source "std::recursive_mutex" 8 8 + (fun q gP => std_recursive_mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_dtor_std_recursive_mutex_ast. + vm_compute. reflexivity. + Qed. End with_std_recursive_mutex. Section with_std_shared_mutex. @@ -247,6 +390,18 @@ Section with_std_shared_mutex. rewrite lock_guard_ctor_std_shared_mutex_ast. vm_compute. reflexivity. Qed. + + Lemma dtor_std_shared_mutex_ok : + denoteModule source ** shared_mutex.unlock_spec |-- + lock_guard.dtor_spec "std::shared_mutex" source. + Proof. + apply (dtor_template_ok source "std::shared_mutex" 8 8 + (fun q gP => shared_mutex.R gP.1 q gP.2)). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_dtor_std_shared_mutex_ast. + vm_compute. reflexivity. + Qed. End with_std_shared_mutex. Section with_std_unique_lock_mutex. @@ -308,6 +463,31 @@ Section with_std_unique_lock_mutex. ework with br_erefl. Qed. + Lemma unique_lock_mutex_unlock_spec : + unique_lock.unlock_spec "std::mutex" source |-- + inner_unlock_spec "std::unique_lock"%cpp_name unique_lock_mutexR. + Proof. + unfold unique_lock.unlock_spec, inner_unlock_spec. + rewrite specify_notation.template_specify.unlock. + cbn. + have -> : resolve_value source "std::unique_lock"%cpp_name = None + by vm_compute. + change (specify.exact.method "std::unique_lock::unlock()" + function_qualifiers.N Tvoid [] + (\this this + \pre{mm} this |-> unique_lockR 1$m (Some mm) + \require mm.(unique_lock.is_held) + \pre{K} do_unlock "std::mutex" mm.(unique_lock.mutex_ptr) + mm.(unique_lock.mutex_m) K + \post this |-> unique_lockR 1$m + (Some (mm &: unique_lock._is_held .= false)%lens) ** K) |-- + inner_unlock_spec "std::unique_lock"%cpp_name unique_lock_mutexR). + iApply specify_mono; intros this xs Q. + unfold unlock_basic_lockable, unique_lock_mutex_do_unlock. + cbn. + ework with br_erefl. + Qed. + Lemma ctor_std_unique_lock_mutex_ok : denoteModule source ** unique_lock.lock_spec "std::mutex" source |-- lock_guard.ctor_spec (mutexR := unique_lock_mutexR) @@ -321,4 +501,18 @@ Section with_std_unique_lock_mutex. rewrite lock_guard_ctor_std_unique_lock_mutex_ast. vm_compute. reflexivity. Qed. + + Lemma dtor_std_unique_lock_mutex_ok : + denoteModule source ** unique_lock.unlock_spec "std::mutex" source |-- + lock_guard.dtor_spec (mutexR := unique_lock_mutexR) + "std::unique_lock" source. + Proof. + rewrite unique_lock_mutex_unlock_spec. + apply (dtor_template_ok source "std::unique_lock"%cpp_name 8 8 + unique_lock_mutexR). + - vm_compute. reflexivity. + - unfold genv_tu.find_code. + rewrite lock_guard_dtor_std_unique_lock_mutex_ast. + vm_compute. reflexivity. + Qed. End with_std_unique_lock_mutex. diff --git a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v index 2a6d08ba..5586ec66 100644 --- a/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v +++ b/rocq-brick-libstdcpp/proof/mutex/spec/lock_guard.v @@ -89,12 +89,15 @@ NES.Begin lock_guard. Definition SpecFor_ctor_spec := RegisterSpec (ctor_spec source). #[global] Existing Instance SpecFor_ctor_spec. - cpp.spec "std::lock_guard<$ty>::~lock_guard()" as dtor_spec from source templates templates ( - \\with + Definition dtor_spec (tu : translation_unit) : mpred := + specify.template.dtor ("std::lock_guard" .<< Atype ty >>) $ \this this \pre{m} this |-> R 1$m m \pre{K} do_unlock ty m.(mutex_ptr) m.(mutex_m) K - \post K ** m.(mutex_ptr) |-> mutexR m.(mutex_q)$m m.(mutex_m) - ). + \post K ** m.(mutex_ptr) |-> mutexR m.(mutex_q)$m m.(mutex_m). + #[global] Hint Opaque dtor_spec : sl_opacity. + #[global] Arguments dtor_spec : simpl never. + Definition SpecFor_dtor_spec := RegisterSpec (dtor_spec source). + #[global] Existing Instance SpecFor_dtor_spec. End with_cpp. NES.End lock_guard. From ee7f4dea020f65a5908f6cbdfbf4af575b5e38c7 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 29 Oct 2026 19:46:04 +0000 Subject: [PATCH 106/109] [Codex] Mark adapted LLVM mutex test sources as modified Written by Codex. Add prominent per-file adaptation notices while preserving the upstream LLVM license headers and source provenance. --- .../test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/mutex/lock.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/mutex/try_lock.pass.cpp | 3 +++ .../mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp | 3 +++ .../libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp | 3 +++ .../mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp | 3 +++ .../mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp | 3 +++ .../mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp | 3 +++ .../test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp | 3 +++ .../mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp | 3 +++ 17 files changed, 51 insertions(+) diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp index beb70c2b..055784de 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/assign.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp index 8873a313..470cd949 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.copy.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp index b8f50d72..831361ea 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/ctor.default.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp index 2daed7be..07f112bb 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03 diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp index 17fd2f62..344abe0f 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/mutex/try_lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03 diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp index fec3c2a0..57fdc516 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/assign.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp index 60066837..ef015514 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.copy.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp index bccc6a1d..3575228e 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/ctor.default.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp index b66c61c0..cc12193c 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03 diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp index 8319852c..9966bef4 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/recursive_mutex/try_lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03 diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp index 0d31011d..17d2bf80 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/assign.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp index f66cb9cb..e66ae61f 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.copy.compile.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp index 4b09b3d6..5d612763 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/ctor.default.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp index 53dfe5b3..1c2b24de 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp index 009ec6a6..0aca5a13 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/lock_shared.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp index ec853353..ec4f4f82 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads diff --git a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp index 1ba9abb5..9ba1cfa2 100644 --- a/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp +++ b/rocq-brick-libstdcpp/test/mutex/libcpp_tests/shared_mutex/try_lock_shared.pass.cpp @@ -4,6 +4,9 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // +// Modified by SkyLabs AI: adapted the mutex type and includes for the +// custom mutex conformance tests. See README.txt and UPSTREAM.json. +// //===----------------------------------------------------------------------===// // UNSUPPORTED: no-threads From ac79b86cadc6940af91bd5c8f9ce4be082d296d6 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 29 Oct 2026 19:49:51 +0000 Subject: [PATCH 107/109] [Codex] Fix recursive mutex sentinel linkage across translation units Written by Codex. Make id_hash a C++17 inline variable and guard the entire header. Add a Dune runtest regression that includes the header twice, links two translation units, checks the shared sentinel address, and exercises recursive locking. --- rocq-brick-libstdcpp/test/dune | 6 +++ .../test/mutex/custom_recursive_mutex.hpp | 5 +- .../test/mutex/recursive_mutex_odr.py | 47 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 rocq-brick-libstdcpp/test/mutex/recursive_mutex_odr.py diff --git a/rocq-brick-libstdcpp/test/dune b/rocq-brick-libstdcpp/test/dune index 2de85b3c..da8f0f5c 100644 --- a/rocq-brick-libstdcpp/test/dune +++ b/rocq-brick-libstdcpp/test/dune @@ -37,6 +37,12 @@ )) (include dune.inc) +(rule + (alias runtest) + (action + (run python3 %{dep:mutex/recursive_mutex_odr.py} %{bin:clang++} + %{dep:mutex/custom_recursive_mutex.hpp}))) + (rule (action (with-stdout-to dune.inc.gen (run ./dune-gen.sh))) (deps (glob_files_rec *.{h,c,hpp,cpp,sh}))) diff --git a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp index fa6dc621..92466b3d 100644 --- a/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp +++ b/rocq-brick-libstdcpp/test/mutex/custom_recursive_mutex.hpp @@ -1,10 +1,11 @@ +#pragma once + #include #include #include #include -size_t id_hash = std::hash{}(std::thread::id{}); -#pragma once +inline size_t id_hash = std::hash{}(std::thread::id{}); class MyRecursiveMutex { // not protected by the lock, but atomic std::atomic m_owner{id_hash}; diff --git a/rocq-brick-libstdcpp/test/mutex/recursive_mutex_odr.py b/rocq-brick-libstdcpp/test/mutex/recursive_mutex_odr.py new file mode 100644 index 00000000..d705948f --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/recursive_mutex_odr.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Check that the recursive mutex header links across translation units.""" + +from pathlib import Path +import subprocess +import sys +import tempfile + + +compiler, header = sys.argv[1:] +header = Path(header).resolve() +with tempfile.TemporaryDirectory(prefix="brick-recursive-mutex-odr-") as directory: + root = Path(directory) + # Include the header in two translation units and check that they share the + # sentinel variable, rather than giving it internal linkage in each unit. + (root / "first.cpp").write_text(''' +#include "custom_recursive_mutex.hpp" +#include "custom_recursive_mutex.hpp" +size_t* sentinel_address() { return &id_hash; } +void exercise_mutex() { + MyRecursiveMutex mutex; + mutex.lock(); + mutex.lock(); + mutex.unlock(); + mutex.unlock(); +} +''') + (root / "second.cpp").write_text(''' +#include "custom_recursive_mutex.hpp" +size_t* sentinel_address(); +void exercise_mutex(); +int main() { + if (sentinel_address() != &id_hash) + return 1; + exercise_mutex(); + MyRecursiveMutex mutex; + if (!mutex.try_lock()) + return 2; + mutex.unlock(); +} +''') + executable = root / "odr-test" + subprocess.run([compiler, "-std=c++17", "-stdlib=libstdc++", "-pthread", + "-I", str(header.parent), str(root / "first.cpp"), + str(root / "second.cpp"), "-o", str(executable)], + check=True, timeout=60) + subprocess.run([str(executable)], check=True, timeout=20) From be3b47a89fff76cb5b33984be305dfe1fe6e587c Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 29 Oct 2026 19:49:54 +0000 Subject: [PATCH 108/109] [Codex] Track the thread-ID hash injectivity proof obligation Written by Codex. Document that the standard Hash requirements allow collisions and that owner discrimination requires a target-specific injectivity argument, including the default thread ID, or a collision-free identity. --- rocq-brick-libstdcpp/proof/thread/spec.v | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rocq-brick-libstdcpp/proof/thread/spec.v b/rocq-brick-libstdcpp/proof/thread/spec.v index 0fe1e071..1556a9a8 100644 --- a/rocq-brick-libstdcpp/proof/thread/spec.v +++ b/rocq-brick-libstdcpp/proof/thread/spec.v @@ -104,7 +104,11 @@ Module thread_id_specs. (* Value of thread::id(). Does not represent any thread. *) Parameter default_thread_id : thread_idT. - (* Assume some injective hash function. *) + (* TODO: justify hash injectivity for each supported standard-library/ABI + implementation, including the default (non-thread) ID. The C++ Hash + requirements allow collisions; injectivity does not follow from them. + MyRecursiveMutex relies on this assumption to distinguish owners. If it + cannot be discharged, use a collision-free thread identity instead. *) Parameter thread_id_hash : thread_idT -> Z. Axiom thread_id_hash_injective : forall x y, thread_id_hash x = thread_id_hash y -> x = y. From 57bce7f5857f55c8f93887ff76218da908cae472 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 29 Oct 2026 20:12:43 +0000 Subject: [PATCH 109/109] [Codex] Add scoped_lock contract test and counterexample Written by Codex. Check the existing generic constructor contract with distinct lock and try_lock effects, and specialize its false-flag premises to the incorrect both-true postcondition. Add a native libstdc++ CountingMutex witness under Dune runtest and document the diagnostic scope and unresolved contract issue. --- rocq-brick-libstdcpp/test/dune | 7 ++ rocq-brick-libstdcpp/test/dune-gen.sh | 2 +- .../test/mutex/scoped_lock_counterexample.cpp | 49 +++++++++ .../test/mutex/scoped_lock_counterexample.md | 35 ++++++ .../test/mutex/scoped_lock_counterexample.py | 22 ++++ .../test/mutex/scoped_lock_counterexample.v | 103 ++++++++++++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.cpp create mode 100644 rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.md create mode 100644 rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.py create mode 100644 rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.v diff --git a/rocq-brick-libstdcpp/test/dune b/rocq-brick-libstdcpp/test/dune index da8f0f5c..a536cae8 100644 --- a/rocq-brick-libstdcpp/test/dune +++ b/rocq-brick-libstdcpp/test/dune @@ -37,6 +37,13 @@ )) (include dune.inc) +; An executable counterexample to the current generic scoped_lock contract. +(rule + (alias runtest) + (action + (run python3 %{dep:mutex/scoped_lock_counterexample.py} %{bin:clang++} + %{dep:mutex/scoped_lock_counterexample.cpp}))) + (rule (alias runtest) (action diff --git a/rocq-brick-libstdcpp/test/dune-gen.sh b/rocq-brick-libstdcpp/test/dune-gen.sh index d9396135..3b1662c6 100755 --- a/rocq-brick-libstdcpp/test/dune-gen.sh +++ b/rocq-brick-libstdcpp/test/dune-gen.sh @@ -17,7 +17,7 @@ echo "; DO NOT EDIT: Generated by \"$0${1+ $*}\"" # Temporary: `cpp2v --templates` for only those tests in ./templates. # The custom mutex conformance suites are compiled and run natively, not by cpp2v. -find . \( -path './mutex/libcpp_tests' \) -prune -o -name '*.[ch]pp' -print | +find . \( -path './mutex/libcpp_tests' -o -path './mutex/scoped_lock_counterexample.cpp' \) -prune -o -name '*.[ch]pp' -print | LC_ALL=C sort | while read i; do ./cpp2v-dune-gen.sh -s -- $i -- -std=c++20 -stdlib=libstdc++ diff --git a/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.cpp b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.cpp new file mode 100644 index 00000000..cbc25622 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2026 SkyLabs AI, Inc. +// Distributed under the BSD 3-Clause license; see LICENSE. + +#include +#include +#include + +// A Lockable that distinguishes lock() from successful try_lock(). Both +// operations acquire the same underlying mutex, but only lock() increments +// lock_calls. Atomic counters preserve the Lockable's thread safety. +struct CountingMutex { + std::mutex native; + std::atomic lock_calls{0}; + std::atomic try_lock_calls{0}; + + void lock() { + native.lock(); + ++lock_calls; + } + + bool try_lock() { + ++try_lock_calls; + return native.try_lock(); + } + + void unlock() { native.unlock(); } +}; + +int main() { + CountingMutex a, b; + { + std::scoped_lock guard(a, b); + const unsigned a_locks = a.lock_calls.load(); + const unsigned b_locks = b.lock_calls.load(); + const unsigned tries = a.try_lock_calls.load() + b.try_lock_calls.load(); + std::printf("lock() calls: %u, %u; try_lock() calls: %u\n", + a_locks, b_locks, tries); + + // The current generic ctor_spec transfers both do_lock continuations. + // Choosing the effect 'lock_calls > 0' would incorrectly promise this: + const bool both_lock_effects = a_locks > 0 && b_locks > 0; + std::printf("both lock() effects: %s\n", both_lock_effects ? "true" : "false"); + + // This test targets the supported libstdc++ implementation. The standard + // leaves the sequence and order unspecified; neither argument order is + // assumed here. A different std::lock algorithm may need another witness. + return both_lock_effects || tries == 0 ? 1 : 0; + } +} diff --git a/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.md b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.md new file mode 100644 index 00000000..67f62523 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.md @@ -0,0 +1,35 @@ +The current generic `scoped_lock.ctor_spec` promises both `do_lock` +continuations for a two-mutex constructor. The C++ constructor calls +`std::lock`, which may use `lock`, `try_lock`, and `unlock` in an unspecified +sequence. The `BasicLockable` and `Lockable` classes currently impose no laws +that make these operations interchangeable with respect to their effects. + +`scoped_lock_counterexample.cpp` supplies a thread-safe Lockable wrapper +around `std::mutex`. Only `lock()` increments `lock_calls`; `try_lock()` +acquires the same mutex without that effect. On the supported libstdc++ +implementation, the scoped constructor acquires one mutex with `lock()` and +the other with `try_lock()`. Thus the continuation `lock_calls > 0` is false +for at least one mutex, contradicting the generic contract. The executable +checks this without assuming argument order. Its expectation is specific to +libstdc++; another conforming algorithm could require a different witness. + +`scoped_lock_counterexample.v` checks the contract issue independently of +the native implementation. It instantiates the existing typeclasses with +distinct flag effects, proves that the constructor's locking premises can +be supplied, checks the expansion of the actual exported constructor +contract, and proves that a false flag conflicts with its promised true +effect. This is a contract-level model: it does not verify the native mutex +implementation or prove a linked C++ program satisfies `False`. It introduces +no additional axiom or admitted lemma. The generic contract remains unfixed. + +The native check runs under the Dune `runtest` alias. It can also be run with: + +``` +python3 scoped_lock_counterexample.py clang++ scoped_lock_counterexample.cpp +``` + +When fixing the contract, replace the diagnostic contract equality and +counterexample expectation with a regression that rejects the false client +claim while accepting ordinary scoped locking. A repair needs coherent +relations between locking, successful/failed try-locking, and rollback; +changing the constructor to request `do_try_lock` alone is insufficient. diff --git a/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.py b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.py new file mode 100644 index 00000000..9694ad45 --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Compile and run the scoped_lock contract counterexample with libstdc++.""" +import pathlib +import subprocess +import sys +import tempfile + + +def main() -> None: + cxx, source = sys.argv[1:] + with tempfile.TemporaryDirectory(prefix="scoped-lock-counterexample-") as tmp: + executable = pathlib.Path(tmp) / "counterexample" + subprocess.run( + [cxx, "-std=c++17", "-stdlib=libstdc++", "-pthread", "-O1", + source, "-o", str(executable)], + check=True, timeout=60, + ) + subprocess.run([str(executable)], check=True, timeout=20) + + +if __name__ == "__main__": + main() diff --git a/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.v b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.v new file mode 100644 index 00000000..b7df874e --- /dev/null +++ b/rocq-brick-libstdcpp/test/mutex/scoped_lock_counterexample.v @@ -0,0 +1,103 @@ +(* Copyright (c) 2026 SkyLabs AI, Inc. + Distributed under the BSD 3-Clause license; see LICENSE. *) +Require Import skylabs.auto.cpp.prelude.proof. +Require Import skylabs.brick.libstdcpp.mutex.requirements. +Require Import skylabs.brick.libstdcpp.mutex.spec.scoped_lock. + +(** A contract-level counterexample, paired with the native C++ witness. + The model isolates the flag [lock_calls > 0]: [lock()] sets it, whereas + [try_lock()] leaves it alone. It does not verify the underlying mutex + or claim a linked C++ proof of [False]. *) +Section counterexample. + Context `{Σ : cpp_logic, σ : genv}. + + Definition counting_ty : type := "CountingMutex"%cpp_type. + Definition counting_R (q : cQp.t) (_ : ptr) : Rep := + structR "CountingMutex" q. + Definition called (flag : ptr) (b : bool) : mpred := + flag |-> primR Tbool 1$m (Vbool b). + + #[local] Instance counting_basic_lockable : + BasicLockable counting_ty counting_R := + {| do_lock := fun _ flag K => called flag false ** (called flag true -* K) + ; do_unlock := fun _ _ K => K |}. + #[local] Instance counting_lockable : Lockable counting_ty counting_R := + {| do_try_lock := fun _ _ K => (∀ b : bool, K b)%I |}. + + (** Merely registering Lockable imposes no relation between these effects. + Two false flags supply the ctor's do_lock premises with true flags + as continuations. *) + Lemma supplies_lock_effect this flag : + called flag false |-- + do_lock counting_ty this flag (called flag true). + Proof. cbn. iIntros "$". iIntros "$". Qed. + + Lemma try_lock_can_preserve_false this flag : + called flag false |-- + do_try_lock counting_ty this flag (fun _ => called flag false). + Proof. cbn. iIntros "H" (b). iExact "H". Qed. + + (** Check the existing exported ctor_spec, rather than introduce an + assumed scoped_lock spec. It returns K1 and K2 although try_lock + need not establish either lock-specific effect. A repair must + revisit this diagnostic test. *) + Lemma current_ctor_contract : + scoped_lock.ctor_spec counting_ty counting_ty + (mutexR1 := counting_R) (mutexR2 := counting_R) = + specify.template.ctor + ("std::scoped_lock" .<< Apack [Atype counting_ty; Atype counting_ty] >>) + [Tref counting_ty; Tref counting_ty] $ + \this this + \arg{mp1} "" (Vptr mp1) + \pre{q1 flag1} mp1 |-> counting_R q1$m flag1 + \pre{K1} called flag1 false ** (called flag1 true -* K1) + \arg{mp2} "" (Vptr mp2) + \pre{q2 flag2} mp2 |-> counting_R q2$m flag2 + \pre{K2} called flag2 false ** (called flag2 true -* K2) + \post this |-> scoped_lock.R counting_ty counting_ty counting_R counting_R + 1$m (scoped_lock.Mk mp1 q1 flag1, scoped_lock.Mk mp2 q2 flag2) ** K1 ** K2. + Proof. reflexivity. Qed. + + Lemma ctor_promises_both_lock_effects : + scoped_lock.ctor_spec counting_ty counting_ty + (mutexR1 := counting_R) (mutexR2 := counting_R) |-- + specify.template.ctor + ("std::scoped_lock" .<< Apack [Atype counting_ty; Atype counting_ty] >>) + [Tref counting_ty; Tref counting_ty] $ + \this this + \arg{mp1} "" (Vptr mp1) + \pre{q1 flag1} mp1 |-> counting_R q1$m flag1 + \pre called flag1 false + \arg{mp2} "" (Vptr mp2) + \pre{q2 flag2} mp2 |-> counting_R q2$m flag2 + \pre called flag2 false + \post this |-> scoped_lock.R counting_ty counting_ty counting_R counting_R + 1$m (scoped_lock.Mk mp1 q1 flag1, scoped_lock.Mk mp2 q2 flag2) ** + called flag1 true ** called flag2 true. + Proof. + rewrite current_ctor_contract /specify.template.ctor /specify.exact.ctor. + apply specify_mono. intros this args post. cbn. + iIntros "H". + iDestruct "H" as (mp1 q1 flag1 mp2 q2 flag2) + "(%Hargs & R1 & F1 & R2 & F2 & Hpost)". + iExists mp1, q1, flag1, (called flag1 true), + mp2, q2, flag2, (called flag2 true). + iFrame "R1 R2 Hpost". iSplit; first done. + iSplitL "F1". + - iApply (supplies_lock_effect mp1 flag1). iExact "F1". + - iApply (supplies_lock_effect mp2 flag2). iExact "F2". + Qed. + + (** A legal lock/try_lock trace leaves one flag false, inconsistent + with the corresponding true flag promised by the constructor. + The two half permissions add to one, so disagreement of the values, + rather than overfull ownership, causes the contradiction. *) + Lemma false_flag_contradicts_promised_effect (flag : ptr) : + flag |-> primR Tbool (1 / 2)$m (Vbool false) ** + flag |-> primR Tbool (1 / 2)$m (Vbool true) |-- False. + Proof. + iIntros "[Hfalse Htrue]". + iDestruct (observe_2 [| Vbool false = Vbool true |] + with "Hfalse Htrue") as %[=]. + Qed. +End counterexample.