Re: [PATCH] tools/memory-model: Document herd7 (internal) representation

From: Alan Stern
Date: Sat May 25 2024 - 15:37:31 EST


On Fri, May 24, 2024 at 05:37:06PM +0200, Andrea Parri wrote:
> > - While checking the information below using herd7, I've observed some
> > "strange" behavior with spin_is_locked() (perhaps, unsurprisingly...);
> > IAC, that's also excluded from this table/submission.
>
> For completeness, the behavior in question:
>
> $ cat T.litmus
> C T
>
> {}
>
> P0(spinlock_t *x)
> {
> int r0;
>
> spin_lock(x);
> spin_unlock(x);
> r0 = spin_is_locked(x);
> }
>
> $ herd7 -conf linux-kernel.cfg T.litmus
> Test T Required
> States 0
> Ok
> Witnesses
> Positive: 0 Negative: 0
> Condition forall (true)
> Observation T Never 0 0
> Time T 0.00
> Hash=6fa204e139ddddf2cb6fa963bad117c0
>
> Haven't been using spin_is_locked for a while... perhaps I'm doing
> something wrong? (IAC, will have a closer look next week...)

It turns out the problem lies in the way lock.cat tries to calculate the
rf relation for RU events (a spin_is_locked() that returns False). The
method it uses amounts to requiring that such events must read from the
lock's initial value or an LU event (a spin_unlock()) in a different
thread. This clearly is wrong, and glaringly so in this litmus test
since there are no other threads!

A patch to fix the problem and reorganize the code a bit for greater
readability is below. I'd appreciate it if people could try it out on
various locking litmus tests in our archives.

Alan


---
tools/memory-model/lock.cat | 61 +++++++++++++++++++++++++-------------------
1 file changed, 36 insertions(+), 25 deletions(-)

Index: usb-devel/tools/memory-model/lock.cat
===================================================================
--- usb-devel.orig/tools/memory-model/lock.cat
+++ usb-devel/tools/memory-model/lock.cat
@@ -54,6 +54,12 @@ flag ~empty LKR \ domain(lk-rmw) as unpa
*)
empty ([LKW] ; po-loc ; [LKR]) \ (po-loc ; [UL] ; po-loc) as lock-nest

+(*
+ * In the same way, spin_is_locked() inside a critical section must always
+ * return True (no RU events can be in a critical section for the same lock).
+ *)
+empty ([LKW] ; po-loc ; [RU]) \ (po-loc ; [UL] ; po-loc) as nested-is-locked
+
(* The final value of a spinlock should not be tested *)
flag ~empty [FW] ; loc ; [ALL-LOCKS] as lock-final

@@ -79,42 +85,47 @@ empty ([UNMATCHED-LKW] ; loc ; [UNMATCHE
(* rfi for LF events: link each LKW to the LF events in its critical section *)
let rfi-lf = ([LKW] ; po-loc ; [LF]) \ ([LKW] ; po-loc ; [UL] ; po-loc)

-(* rfe for LF events *)
+(* Utility macro to convert a single pair to a single-edge relation *)
+let pair-to-relation p = p ++ 0
+
+(*
+ * Given an LF event r outside a critical section, r cannot read
+ * internally but it may read from an LKW event in another thread.
+ * Compute the relation containing these possible edges.
+ *)
+let possible-rfe-noncrit-lf r = (LKW * {r}) & loc & ext
+
+(* Compute set of sets of possible rfe edges for LF events *)
let all-possible-rfe-lf =
- (*
- * Given an LF event r, compute the possible rfe edges for that event
- * (all those starting from LKW events in other threads),
- * and then convert that relation to a set of single-edge relations.
- *)
- let possible-rfe-lf r =
- let pair-to-relation p = p ++ 0
- in map pair-to-relation ((LKW * {r}) & loc & ext)
+ (* Convert the possible-rfe relation for r to a set of single edges *)
+ let set-of-singleton-rfe-lf r =
+ map pair-to-relation (possible-rfe-noncrit-lf r)
(* Do this for each LF event r that isn't in rfi-lf *)
- in map possible-rfe-lf (LF \ range(rfi-lf))
+ in map set-of-singleton-rfe-lf (LF \ range(rfi-lf))

(* Generate all rf relations for LF events *)
with rfe-lf from cross(all-possible-rfe-lf)
let rf-lf = rfe-lf | rfi-lf

(*
- * RU, i.e., spin_is_locked() returning False, is slightly different.
- * We rely on the memory model to rule out cases where spin_is_locked()
- * within one of the lock's critical sections returns False.
+ * Given an RU event r, r may read internally from the last po-previous UL,
+ * or it may read from a UL event in another thread or the initial write.
+ * Compute the relation containing these possible edges.
*)
-
-(* rfi for RU events: an RU may read from the last po-previous UL *)
-let rfi-ru = ([UL] ; po-loc ; [RU]) \ ([UL] ; po-loc ; [LKW] ; po-loc)
-
-(* rfe for RU events: an RU may read from an external UL or the initial write *)
-let all-possible-rfe-ru =
- let possible-rfe-ru r =
- let pair-to-relation p = p ++ 0
- in map pair-to-relation (((UL | IW) * {r}) & loc & ext)
- in map possible-rfe-ru RU
+let possible-rf-ru r = (((UL * {r}) & po-loc) \
+ ([UL] ; po-loc ; [UL] ; po-loc)) |
+ (((UL | IW) * {r}) & loc & ext)
+
+(* Compute set of sets of possible rf edges for RU events *)
+let all-possible-rf-ru =
+ (* Convert the possible-rf relation for r to a set of single edges *)
+ let set-of-singleton-rf-ru r =
+ map pair-to-relation (possible-rf-ru r)
+ (* Do this for each RU event r *)
+ in map set-of-singleton-rf-ru RU

(* Generate all rf relations for RU events *)
-with rfe-ru from cross(all-possible-rfe-ru)
-let rf-ru = rfe-ru | rfi-ru
+with rf-ru from cross(all-possible-rf-ru)

(* Final rf relation *)
let rf = rf | rf-lf | rf-ru