Am 9/19/2024 um 10:30 PM schrieb Jonas Oberhauser:



Am 9/19/2024 um 2:12 AM schrieb Jann Horn:
On Tue, Sep 17, 2024 at 4:33 PM Boqun Feng <boqun.f...@gmail.com> wrote:
Hazard pointers [1] provide a way to dynamically distribute refcounting
and can be used to improve the scalability of refcounting without
significant space cost.

+static inline void *__hazptr_tryprotect(hazptr_t *hzp,
+                                       void *const *p,
+                                       unsigned long head_offset)
+{
+       void *ptr;
+       struct callback_head *head;
+
+       ptr = READ_ONCE(*p);
+
+       if (ptr == NULL)
+               return NULL;
+
+       head = (struct callback_head *)(ptr + head_offset);
+
+       WRITE_ONCE(*hzp, head);
+       smp_mb();
+
+       ptr = READ_ONCE(*p); // read again
+
+       if (ptr + head_offset != head) { // pointer changed
+               WRITE_ONCE(*hzp, NULL);  // reset hazard pointer
+               return NULL;
+       } else
+               return ptr;
+}

I got nerdsniped by the Plumbers talk. So, about that smp_mb()...

I think you should be able to avoid the smp_mb() using relaxed atomics
(on architectures that have those), at the cost of something like a
cmpxchg-acquire sandwiched between a load-acquire and a relaxed load?
I'm not sure how their cost compares to an smp_mb() though.



We have done a similar scheme before, and on some architectures (not x86) the RMW is slightly cheaper than the mb.

Your reasoning is a bit simplified because it seems to assume a stronger concept of ordering than LKMM has, but even with LKMM's ordering your code seems fine.

I feel it can even be simplified a little, the hazard bit does not seem necessary.

Assuming atomic operations for everything racy, relaxed unless stated otherwise:

(R)eader:

   old = read p // I don't think this needs acq, because of address dependencies (*)
    haz ||=_acq old
    if (read p != old) retry;

I realized before going to bed that I copied a subtle mistake here from Jann's code, we need an address dependency from this read, or it is not ABA safe.
This can be done with the small detour that Boqun used:

head = read p // I don't think this needs acq, because of address dependencies (*)
     haz ||=_acq head
     old = read p // again
     if (head != old) retry;
barrier(); // ensure compiler does not use its knowledge that head == old to do *head instead! *old // definitely use the second read pointer so hardware can't speculatively dereference this before the second read!


Have a good time,
  jonas


Reply via email to