On Mon Mar 31 22:57:12 2025 +0000, Jinoh Kang wrote:
For write side, i agree. For read side, we need an explicit acquire fence before the last uniq load to replace all preceding load-acquires after the initial uniq load-acquire. So it shoud look like this:
static BOOL read_acquire_user_entry( HANDLE handle, unsigned short type, const volatile struct user_entry *src, struct user_entry *dst ) { if (!is_valid_entry_uniq( handle, type, ReadAcquire64( (LONG64 *)&src->uniq ) )) return FALSE; dst->offset = src->offset; dst->tid = src->tid; dst->pid = src->pid ); dst->padding = src->padding; #if defined(__x86_64__) && !defined(__arm64ec__) || defined(__i386__) _ReadWriteBarrier(); #else MemoryBarrier(); #endif dst->uniq = ReadNoFence64( (LONG64 *)&src->uniq ); return is_valid_entry_uniq( handle, type, dst->uniq ); }
Note that the first ReadAcquire stays as-is, but subsequent acquires are replaced by an acquire fence *immediately* before last uniq load. This corresponds to "Figure 6. Seqlock reader with acquire fence" in Hans-J Boehm's MSPC '12 paper "Can seqlocks get along with programming language memory models?" (https://dl.acm.org/doi/10.1145/2247684.2247688). This is consistent with existing seqlock mechanism for desktop/input objects in wine. Also see `read_seqretry` in Linux kernel's seqlock implementation.
I reused the __SHARED_READ_FENCE macro we had already instead, `_ReadWriteBarrier` isn't available on the unix side.