• Consequential RCU

    From Joseph Seigh@3:633/10 to All on Wed Sep 16 18:25:03 2026
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something
    that's been around forever.* It's simple enough that it must be in use somewhere. I just haven't noticed it. I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free. If you have qsbr (RCU) or ebr,
    that would be a better choice. It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Fri Sep 18 16:07:28 2026
    On 9/16/26 6:25 PM, Joseph Seigh wrote:

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    Actually I just though of a 2nd use for this. The POC for the first
    case was more work than I felt like doing. Ditto for the 2nd case.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Sep 19 13:34:45 2026
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something that's been around forever.*˙ It's simple enough that it must be in use somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    Fwiw, I have used seqlocks for the writer side and pure RCU for the
    readers. Actually, there is a way to use seqlocks to gain DWCAS on a
    system that does not support it.

    https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ

    https://pastebin.com/raw/TgTcfYtR



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Sun Sep 20 10:58:00 2026
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something
    that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    Fwiw, I have used seqlocks for the writer side and pure RCU for the
    readers. Actually, there is a way to use seqlocks to gain DWCAS on a
    system that does not support it.

    https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ

    https://pastebin.com/raw/TgTcfYtR



    Java did its AtomicStampedReference by creating an object to hold
    the stamp and the reference and then doing a single word compare
    and swap on a reference to it. All this so you can run on
    hardware which effectively doesn't exist anymore and even if it
    did you would need to backport the os to support obsolete
    hardware so the jvm could even run on it.

    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    Re the OP, I did think of a really good POC for this but it is
    way too much work. I have better things to waste my time
    with. :)





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Sep 20 14:51:16 2026
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    Fwiw, I have used seqlocks for the writer side and pure RCU for the
    readers. Actually, there is a way to use seqlocks to gain DWCAS on a
    system that does not support it.

    https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ

    https://pastebin.com/raw/TgTcfYtR



    Java did its AtomicStampedReference by creating an object to hold
    the stamp and the reference and then doing a single word compare
    and swap on a reference to it.˙ All this so you can run on
    hardware which effectively doesn't exist anymore and even if it
    did you would need to backport the os to support obsolete
    hardware so the jvm could even run on it.

    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    Re the OP, I did think of a really good POC for this but it is
    way too much work.˙ I have better things to waste my time
    with. :)





    ;^D I will look at it, perhaps tonight. Might have some free time.
    Thanks Joe, as always: Your works is rather grand. You taught me a lot
    back in c.p.t. Thanks for that., the SenderX files? ;^o

    Fwiw, I am working on some audio for my special vector fields. Here is
    an example, A Youtube link. also, I will send you a DM over on the god
    damn FB. :^)

    https://youtu.be/gVWAgO5PAfI

    Beam me up? lol. ;^)

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Sep 20 16:09:31 2026
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    Fwiw, I have used seqlocks for the writer side and pure RCU for the
    readers. Actually, there is a way to use seqlocks to gain DWCAS on a
    system that does not support it.

    https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ

    https://pastebin.com/raw/TgTcfYtR



    Java did its AtomicStampedReference by creating an object to hold
    the stamp and the reference and then doing a single word compare
    and swap on a reference to it.˙ All this so you can run on
    hardware which effectively doesn't exist anymore and even if it
    did you would need to backport the os to support obsolete
    hardware so the jvm could even run on it.

    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.

    GAS.

    https://web.archive.org/web/20060214112345/http://appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html

    https://web.archive.org/web/20060214112539/http://appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html

    GAS was kind to me.


    # Copyright 2005 Chris Thomasson


    .align 16
    .globl np_ac_i686_atomic_dwcas_fence
    np_ac_i686_atomic_dwcas_fence:
    pushl %esi
    pushl %ebx
    movl 16(%esp), %esi
    movl (%esi), %eax
    movl 4(%esi), %edx
    movl 20(%esp), %esi
    movl (%esi), %ebx
    movl 4(%esi), %ecx
    movl 12(%esp), %esi
    lock cmpxchg8b (%esi)
    jne np_ac_i686_atomic_dwcas_fence_fail
    xorl %eax, %eax
    popl %ebx
    popl %esi
    ret

    np_ac_i686_atomic_dwcas_fence_fail:
    movl 16(%esp), %esi
    movl %eax, (%esi)
    movl %edx, 4(%esi)
    movl $1, %eax
    popl %ebx
    popl %esi
    ret




    .align 16
    .globl ac_i686_stack_mpmc_push_cas
    ac_i686_stack_mpmc_push_cas:
    movl 4(%esp), %edx
    movl (%edx), %eax
    movl 8(%esp), %ecx

    ac_i686_stack_mpmc_push_cas_retry:
    movl %eax, (%ecx)
    lock cmpxchgl %ecx, (%edx)
    jne ac_i686_stack_mpmc_push_cas_retry
    ret




    .align 16
    .globl np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas:
    pushl %esi
    pushl %ebx

    np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_reload:
    movl 12(%esp), %esi
    movl 4(%esi), %edx
    movl (%esi), %eax

    np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_retry:
    movl 16(%esp), %ebx
    movl %eax, (%ebx)
    mfence
    cmpl (%esi), %eax
    jne np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_reload
    test %eax, %eax
    je np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_fail
    movl (%eax), %ebx
    leal 1(%edx), %ecx
    lock cmpxchg8b (%esi)
    jne np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_retry

    np_ac_i686_lfgc_smr_stack_mpmc_pop_dwcas_fail:
    movl 16(%esp), %esi
    xorl %ebx, %ebx
    movl %ebx, (%esi)
    popl %ebx
    popl %esi
    ret




    .align 16
    .globl np_ac_i686_stack_mpmc_pop_dwcas
    np_ac_i686_stack_mpmc_pop_dwcas:
    pushl %esi
    pushl %ebx
    movl 12(%esp), %esi
    movl 4(%esi), %edx
    movl (%esi), %eax

    np_ac_i686_stack_mpmc_pop_dwcas_retry:
    test %eax, %eax
    je np_ac_i686_stack_mpmc_pop_dwcas_fail
    movl (%eax), %ebx
    leal 1(%edx), %ecx
    lock cmpxchg8b (%esi)
    jne np_ac_i686_stack_mpmc_pop_dwcas_retry

    np_ac_i686_stack_mpmc_pop_dwcas_fail:
    popl %ebx
    popl %esi
    ret




    .align 16
    .globl ac_i686_lfgc_smr_activate
    ac_i686_lfgc_smr_activate:
    movl 4(%esp), %edx
    movl 8(%esp), %ecx

    ac_i686_lfgc_smr_activate_reload:
    movl (%ecx), %eax
    movl %eax, (%edx)
    mfence
    cmpl (%ecx), %eax
    jne ac_i686_lfgc_smr_activate_reload
    ret




    .align 16
    .globl ac_i686_lfgc_smr_deactivate
    ac_i686_lfgc_smr_deactivate:
    movl 4(%esp), %ecx
    xorl %eax, %eax
    movl %eax, (%ecx)
    ret




    .align 16
    .globl ac_i686_queue_spsc_push
    ac_i686_queue_spsc_push:
    movl 4(%esp), %eax
    movl 8(%esp), %ecx
    movl 4(%eax), %edx
    # sfence may be needed here for future x86
    movl %ecx, (%edx)
    movl %ecx, 4(%eax)
    ret




    .align 16
    .globl ac_i686_queue_spsc_pop
    ac_i686_queue_spsc_pop:
    pushl %ebx
    movl 8(%esp), %ecx
    movl (%ecx), %eax
    cmpl 4(%ecx), %eax
    je ac_i686_queue_spsc_pop_failed
    movl (%eax), %edx
    # lfence may be needed here for future x86
    movl 12(%edx), %ebx
    movl %edx, (%ecx)
    movl %ebx, 12(%eax)
    popl %ebx
    ret

    ac_i686_queue_spsc_pop_failed:
    xorl %eax, %eax
    popl %ebx
    ret




    .align 16
    .globl ac_i686_mb_fence
    ac_i686_mb_fence:
    mfence
    ret




    .align 16
    .globl ac_i686_mb_naked
    ac_i686_mb_naked:
    ret




    .align 16
    .globl ac_i686_mb_store_fence
    ac_i686_mb_store_fence:
    movl 4(%esp), %ecx
    movl 8(%esp), %eax
    mfence
    movl %eax, (%ecx)
    ret




    .align 16
    .globl ac_i686_mb_store_naked
    ac_i686_mb_store_naked:
    movl 4(%esp), %ecx
    movl 8(%esp), %eax
    movl %eax, (%ecx)
    ret




    .align 16
    .globl ac_i686_mb_load_fence
    ac_i686_mb_load_fence:
    movl 4(%esp), %ecx
    movl (%ecx), %eax
    mfence
    ret




    .align 16
    .globl ac_i686_mb_load_naked
    ac_i686_mb_load_naked:
    movl 4(%esp), %ecx
    movl (%ecx), %eax
    ret




    .align 16
    .globl ac_i686_atomic_xchg_fence
    ac_i686_atomic_xchg_fence:
    movl 4(%esp), %ecx
    movl 8(%esp), %eax
    xchgl %eax, (%ecx)
    ret




    .align 16
    .globl ac_i686_atomic_xadd_fence
    ac_i686_atomic_xadd_fence:
    movl 4(%esp), %ecx
    movl 8(%esp), %eax
    lock xaddl %eax, (%ecx)
    ret




    .align 16
    .globl ac_i686_atomic_inc_fence
    ac_i686_atomic_inc_fence:
    movl 4(%esp), %ecx
    movl $1, %eax
    lock xaddl %eax, (%ecx)
    incl %eax
    ret




    .align 16
    .globl ac_i686_atomic_dec_fence
    ac_i686_atomic_dec_fence:
    movl 4(%esp), %ecx
    movl $-1, %eax
    lock xaddl %eax, (%ecx)
    decl %eax
    ret




    .align 16
    .globl ac_i686_atomic_cas_fence
    ac_i686_atomic_cas_fence:
    movl 4(%esp), %ecx
    movl 8(%esp), %eax
    movl 12(%esp), %edx
    lock cmpxchgl %edx, (%ecx)
    ret





    Re the OP, I did think of a really good POC for this but it is
    way too much work.˙ I have better things to waste my time
    with. :)






    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Sun Sep 20 20:19:04 2026
    On 9/20/26 7:09 PM, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:

    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.

    GAS.

    https://web.archive.org/web/20060214112345/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html

    https://web.archive.org/web/20060214112539/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html

    GAS was kind to me.


    https://github.com/jseigh/queues/blob/main/include/atomix.h

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Sep 20 19:26:39 2026
    On 9/20/2026 5:19 PM, Joseph Seigh wrote:
    On 9/20/26 7:09 PM, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:

    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.

    GAS.

    https://web.archive.org/web/20060214112345/http://
    appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html

    https://web.archive.org/web/20060214112539/http://
    appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html

    GAS was kind to me.


    https://github.com/jseigh/queues/blob/main/include/atomix.h

    Excellent! 64-bit. Notice my DWCAS. Looks familiar, right? I basically
    ported yours way back in the day. I think I first saw yours in c.p.t, or
    that asm usenet group you also posted in.

    Returning 0 for success, was just POSIX habits personification 101? ;^D

    In MASM:

    align 16
    np_ac_i686_atomic_dwcas_fence PROC
    push esi
    push ebx
    mov esi, [esp + 16]
    mov eax, [esi]
    mov edx, [esi + 4]
    mov esi, [esp + 20]
    mov ebx, [esi]
    mov ecx, [esi + 4]
    mov esi, [esp + 12]
    lock cmpxchg8b qword ptr [esi]
    jne np_ac_i686_atomic_dwcas_fence_fail
    xor eax, eax
    pop ebx
    pop esi
    ret

    np_ac_i686_atomic_dwcas_fence_fail:
    mov esi, [esp + 16]
    mov [esi + 0], eax;
    mov [esi + 4], edx;
    mov eax, 1
    pop ebx
    pop esi
    ret
    np_ac_i686_atomic_dwcas_fence ENDP

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Sep 21 14:50:19 2026
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.


    You really should learn about inline assembly in gcc. It can be
    significantly more efficient than using an external call in a separately assembled file. And I am confident that you'd find it fun! (Of course,
    it means the code is somewhat non-portable, but portability has never
    been a strength of assembly. The same syntax is supported by clang,
    icc, and some other compilers.)

    To be clear here - I have no comment on the actual use of the
    instructions or the locking algorithms. You and Joseph know these much
    better than I do, especially in the x86 world. I am merely showing what
    I think is a better way to write the code and integrate it with C++, at
    least for gcc and other compilers supporting the same syntax. I am also
    not sure if this is a "strong" or "weak" compare-exchange, or the exact
    memory ordering. That is not really important for showing the inline assembly.

    Your intention (AFAICS) here is to wrap the locked cmpxchg8b and
    cmpxchg16b instructions. The 8-byte version takes a uint64_t * pointer
    "p" and compares it to EDX:EAX. If it is equal, ZF is set and *p is set
    to ECX:EBX. Otherwise ZF is clearer and EDX:EAX is loaded with *p. The 16-byte version is similar, but with RDX:RAX and RCX:RBX. There is also
    the cmpxchg instruction that works takes a T* pointer p and a register
    R, and uses AL/AX/EAX/RAX and register R instead of register pairs. T
    can be up to 32-bit in x86-32, and 64-bit in x86-64.

    Since x86-32 is pretty much outdated, let's stick to x86-64 and make two functions. However, gains from good inline assembly can be higher in
    32-bit mode as it can save a great deal of register/stack manipulation
    dues to the terrible x86-32 ABI for function calls.

    // Code here works for C and C++, though calling this "uint128_t"
    // is cheating a bit.

    typedef unsigned __int128 uint128_t;

    // Semantics - atomically do :
    // if (*p_var == *p_expected) {
    / *p_var = desired;
    // return true;
    // } else {
    / *p_expected = *p_var;
    // return false;
    // }


    bool compare_exchange8(volatile uint64_t * p_var,
    uint64_t * p_expected, uint64_t desired);

    bool compare_exchange16(volatile uint128_t * p_var,
    uint128_t * p_expected, uint128_t desired);


    The 8-byte case is easy - use C++'s standard atomic compare_exchange(),
    or for C11 onwards, use standard atomic_compare_exchange_strong(). For pre-C11/C++11, use gcc's __atomic_compare_exchange_n(). Testing with
    godbolt gives the same code in each case :

    bool cpp_compare_exchange8(volatile std::atomic<uint64_t> * p_var,
    uint64_t * p_expected, uint64_t desired) {

    return p_var->compare_exchange_strong(*p_expected, desired);
    }

    "cpp_compare_exchange8(std::atomic<unsigned long> volatile*, unsigned
    long*, unsigned long)":
    mov rax, QWORD PTR [rsi]
    lock cmpxchg QWORD PTR [rdi], rdx
    je .L8
    mov QWORD PTR [rsi], rax
    .L8:
    sete al
    ret

    Although you clearly should be using the standard library functions
    rather than home-made inline assembly, let's do this with inline
    assembly anyway as an example :

    inline bool inline_compare_exchange8(volatile uint64_t * p_var,
    uint64_t * p_expected, uint64_t desired) {

    bool result;
    uint64_t expected = *p_expected;
    asm volatile(
    "lock cmpxchgq %[desired], (%[p_var])"
    : [result] "=@ccz" (result),
    [expected] "+a" (expected)
    : [p_var] "r" (p_var),
    [desired] "r" (desired)
    : "memory", "cc"
    );
    if (!result) *p_expected = expected;
    return result;
    }

    Key points here include :

    1. Use a "bool" result that matches the "=@ccz" constraint to capture
    the zero flag directly - code that calls this function can immediately
    use "je" or "cmove" instructions, because the compiler knows the result
    is the zero flag.

    2. "expected" is an input/output operand.

    3. Registers are allocated automatically by the compiler, except for "expected" which must use the "A" register (as that's how the "cmpxchgq" instruction works). This means that if calling code already has the
    values in other registers, it uses them directly - no extra register
    moves or stacking.

    4. The operands to the assembly are given explicit names so that the
    code is clearer. (They don't have to match the C names, but it's
    usually easiest if they do.)

    5. Local C variables can be made and used freely - they do not lead to
    any extra costs in the generated code.

    6. The inline assembly itself is absolutely minimal - a single
    instruction. That should always be the aim for inline assembly. The
    more the compiler does itself, the less you have to do manually, and the
    more efficient the end results. It's a win-win situation.


    The generated code here is identical to that of the standard library
    version. In the link below, I also have a variant for -m32 code, and a
    quick test function that shows that the zero flag works as desired.
    There are a couple of additional register moves in the test of the
    32-bit version (compared to the standard library version), but otherwise
    the code is identical and, AFAICS, optimal.

    <https://godbolt.org/z/hEcPzhGr5>


    The 16-byte version is a bit more involved, because the 128-bit
    "expected" and "desired" need to be split up and put in specific
    registers. (Just like the 32-bit version of the 8-byte function.) But
    the same principles apply - do all that manipulation in C++, not
    assembly. This also makes it much easier to adapt to a real function in
    which you might not have 128-bit "expected" and "desired", but four
    separate 64-bit items (values, addresses, etc.).

    inline bool inline_compare_exchange16(volatile uint128_t * p_var,
    uint128_t * p_expected, uint128_t desired) {

    bool result;
    uint128_t expected = *p_expected;
    uint64_t expected_lo = (uint64_t) expected;
    uint64_t expected_hi = (uint64_t) (expected >> 64);
    const uint64_t desired_lo = (uint64_t) desired;
    const uint64_t desired_hi = (uint64_t) (desired >> 64);
    asm volatile(
    "lock cmpxchg16b (%[p_var])"
    : [result] "=@ccz" (result),
    [expected_lo] "+a" (expected_lo),
    [expected_hi] "+d" (expected_hi)
    : [p_var] "r" (p_var),
    [desired_lo] "b" (desired_lo),
    [desired_hi] "c" (desired_hi)
    : "memory", "cc"
    );
    if (!result) {
    expected = ((uint128_t) expected_hi << 64) + expected_lo;
    *p_expected = expected;
    }
    return result;
    }

    Again, there is only one line of assembly here. The generated code is:

    inline_compare_exchange16(unsigned __int128 volatile*, unsigned
    __int128*, unsigned __int128):
    pushq %rbx
    movq (%rsi), %rax
    movq %rdx, %rbx
    movq 8(%rsi), %rdx
    lock cmpxchg16b (%rdi)
    je .L12
    movq %rax, (%rsi)
    movq %rdx, 8(%rsi)
    .L12:
    sete %al
    popq %rbx
    ret

    This is a bit more efficient than Joseph's version. The gains will be
    more noticeable in code that uses the function as there will be fewer
    register moves, no need to push variables out of registers and onto the
    stack before loading them into registers again, and the zero flag can be
    used directly.


    David






    https://web.archive.org/web/20060214112345/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html

    https://web.archive.org/web/20060214112539/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html

    GAS was kind to me.


    # Copyright 2005 Chris Thomasson


    .align 16
    .globl np_ac_i686_atomic_dwcas_fence
    np_ac_i686_atomic_dwcas_fence:
    ˙ pushl %esi
    ˙ pushl %ebx
    ˙ movl 16(%esp), %esi
    ˙ movl (%esi), %eax
    ˙ movl 4(%esi), %edx
    ˙ movl 20(%esp), %esi
    ˙ movl (%esi), %ebx
    ˙ movl 4(%esi), %ecx
    ˙ movl 12(%esp), %esi
    ˙ lock cmpxchg8b (%esi)
    ˙ jne np_ac_i686_atomic_dwcas_fence_fail
    ˙ xorl %eax, %eax
    ˙ popl %ebx
    ˙ popl %esi
    ˙ ret

    np_ac_i686_atomic_dwcas_fence_fail:
    ˙ movl 16(%esp), %esi
    ˙ movl %eax, (%esi)
    ˙ movl %edx, 4(%esi)
    ˙ movl $1, %eax
    ˙ popl %ebx
    ˙ popl %esi
    ret





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Sep 21 15:47:10 2026
    On Mon, 21 Sep 2026 14:50:19 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.


    You really should learn about inline assembly in gcc. It can be >significantly more efficient than using an external call in a separately >assembled file. And I am confident that you'd find it fun! (Of course,

    Unless the assembler is just a few opcodes you're better off leaving it to
    a compiler as it'll usually do a better job and probably knows opcodes you've never even heard of.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Sep 21 18:45:19 2026
    On 21/09/2026 17:47, boltar@caprica.universe wrote:
    On Mon, 21 Sep 2026 14:50:19 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good
    ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course,

    Unless the assembler is just a few opcodes you're better off leaving it to
    a compiler as it'll usually do a better job and probably knows opcodes you've
    never even heard of.


    The point here was an opcode - the 16 byte compare-and-exchange - that
    the compiler does /not/ know about, and is not supported by the C++ (or
    C) standard library for atomics. And the point of the inline assembly I
    gave is that it is just one line of actual assembly, precisely because
    the compiler does a better job of all the "housekeeping" stuff of moving things in and out of registers than you can do with manual assembly code.

    Next time, perhaps you might like to read more than the first two lines
    of a post before replying to it.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Sep 24 15:28:32 2026
    On 9/21/2026 5:50 AM, David Brown wrote:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good ol
    GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be significantly more efficient than using an external call in a separately assembled file.˙ And I am confident that you'd find it fun!˙ (Of course, [...]

    Yeah. Well. Shit. Back then I did not want any compiler to mess with
    things, and my inline skills were rather lacking! I felt way better
    making a header the structs, and externally assembling things. Keep in
    mind this was before C/C++ 11. I even said woe to LTO.

    Iirc this was my abstraction for C over it:

    https://web.archive.org/web/20060214112519/http://appcore.home.comcast.net/appcore/include/cpu/i686/ac_i686_h.html

    Just a simple layer for C to connect to my ASM:

    /* Copyright 2005 Chris Thomasson */


    #ifndef AC_I686_H
    #define AC_I686_H


    #ifdef __cplusplus
    extern "C"
    {
    #endif




    /***** i686 Specific API *****/
    #define AC_I686_CACHE_LINE 128
    #define AC_I686_WORD_SIZE 4
    #define AC_DECLSPEC_ALIGN_CACHE_LINE AC_DECLSPEC_ALIGN( AC_I686_CACHE_LINE )


    #ifdef _MSC_VER
    typedef signed __int32 ac_i686_intword_t;
    typedef unsigned __int32 ac_i686_uintword_t;
    typedef signed __int16 ac_i686_intword2_t;
    typedef unsigned __int16 ac_i686_uintword2_t;
    #define ac_i686_pause() _asm pause

    #else
    typedef signed int ac_i686_intword_t;
    typedef unsigned int ac_i686_uintword_t;
    typedef signed int short ac_i686_intword2_t;
    typedef unsigned int short ac_i686_uintword2_t;
    #define ac_i686_pause() __asm__ __volatile__ ( "pause" )
    #endif


    typedef ac_i686_uintword_t ac_i686_flags_t;


    #define ac_i686_pause_yield() ac_i686_pause(); sched_yield()




    #ifdef _MSC_VER
    /* 4324: structure was padded due to __declspec(align()) */
    #pragma warning ( disable : 4324 )
    #pragma pack(1)
    #endif

    /* MUST be four adjacent words */
    typedef struct
    AC_DECLSPEC_PACKED
    ac_i686_node_
    {
    struct ac_i686_node_ *next;
    struct ac_i686_node_ *lfgc_next;
    ac_fp_dtor_t fp_dtor;
    const void *state;

    } ac_i686_node_t;


    /* MUST be two adjacent words. front must be at offset 0 */
    typedef struct
    AC_DECLSPEC_PACKED
    ac_i686_stack_mpmc_
    {
    ac_i686_node_t *front;
    ac_i686_uintword_t aba;

    } ac_i686_stack_mpmc_t;


    /* MUST be two adjacent words. front must be at offset 0 */
    typedef struct
    AC_DECLSPEC_PACKED
    ac_i686_queue_spsc_
    {
    ac_i686_node_t *front;
    ac_i686_node_t *back;

    } ac_i686_queue_spsc_t;


    /* MUST be two adjacent words */
    typedef struct
    AC_DECLSPEC_PACKED
    ac_i686_tls_
    {
    struct ac_i686_lfgc_smr_ *lfgc_smr;
    ac_thread_t *thread;

    } ac_i686_tls_t;


    #ifdef _MSC_VER
    #pragma pack()
    /* 4324: structure was padded due to __declspec(align()) */
    #pragma warning ( default : 4324 )
    #endif




    /* critical i686 atomic api compile time assertion */
    AC_BUILD_DBG_ASSERT
    ( i686,
    sizeof( void* ) == AC_I686_WORD_SIZE &&
    sizeof( ac_fp_dtor_t ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_node_t* ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_tls_t* ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_queue_spsc_t* ) == AC_I686_WORD_SIZE &&
    sizeof( struct ac_i686_lfgc_smr_* ) == AC_I686_WORD_SIZE &&
    sizeof( ac_thread_t* ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_intword_t ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_uintword_t ) == AC_I686_WORD_SIZE &&
    sizeof( ac_i686_intword2_t ) == AC_I686_WORD_SIZE / 2 &&
    sizeof( ac_i686_uintword2_t ) == AC_I686_WORD_SIZE / 2 &&
    sizeof( ac_i686_node_t ) == AC_I686_WORD_SIZE * 4 &&
    sizeof( ac_i686_queue_spsc_t ) == AC_I686_WORD_SIZE * 2 &&
    sizeof( ac_i686_tls_t ) == AC_I686_WORD_SIZE * 2 &&
    sizeof( ptrdiff_t ) == AC_I686_WORD_SIZE &&
    sizeof( size_t ) == AC_I686_WORD_SIZE );




    AC_SYS_APIEXPORT
    void AC_CDECL
    ac_i686_mb_fence
    ( void );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_mb_load_fence
    ( ac_i686_intword_t* );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_mb_store_fence
    ( ac_i686_intword_t*,
    ac_i686_intword_t );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_mb_load_naked
    ( ac_i686_intword_t* );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_mb_store_naked
    ( ac_i686_intword_t*,
    ac_i686_intword_t );


    AC_SYS_APIEXPORT
    int AC_CDECL
    np_ac_i686_atomic_dwcas_fence
    ( void*,
    void*,
    const void* );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_atomic_cas_fence
    ( ac_i686_intword_t*,
    ac_i686_intword_t,
    ac_i686_intword_t );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_atomic_xchg_fence
    ( ac_i686_intword_t*,
    ac_i686_intword_t );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_atomic_xadd_fence
    ( ac_i686_intword_t*,
    ac_i686_intword_t );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_atomic_inc_fence
    ( ac_i686_intword_t* );


    AC_SYS_APIEXPORT
    ac_i686_intword_t AC_CDECL
    ac_i686_atomic_dec_fence
    ( ac_i686_intword_t* );


    AC_SYS_APIEXPORT void AC_CDECL
    ac_i686_stack_mpmc_push_cas
    ( ac_i686_stack_mpmc_t*,
    ac_i686_node_t* );


    AC_SYS_APIEXPORT ac_i686_node_t* AC_CDECL
    np_ac_i686_stack_mpmc_pop_dwcas
    ( ac_i686_stack_mpmc_t* );


    AC_SYS_APIEXPORT void AC_CDECL
    ac_i686_queue_spsc_push
    ( ac_i686_queue_spsc_t*,
    ac_i686_node_t* );


    AC_SYS_APIEXPORT ac_i686_node_t* AC_CDECL
    ac_i686_queue_spsc_pop
    ( ac_i686_queue_spsc_t* );


    AC_APIEXPORT ac_i686_node_t* AC_APIDECL
    ac_i686_node_cache_pop
    ( const void *state );


    AC_APIEXPORT void AC_APIDECL
    ac_i686_node_cache_push
    ( ac_i686_node_t* );


    AC_APIEXPORT int AC_APIDECL
    ac_i686_node_cache_push_no_free
    ( ac_i686_node_t* );




    #define ac_i686_stack_mpmc_init( ac_macro_this ) \
    (ac_macro_this)->front = 0


    #define ac_i686_queue_spsc_init( ac_macro_this, ac_macro_dummy ) \
    (ac_macro_this)->front = (ac_macro_dummy); \
    (ac_macro_this)->back = (ac_macro_dummy)


    #define ac_i686_node_init( ac_macro_this, ac_macro_fp_dtor,
    ac_macro_state ) \
    (ac_macro_this)->next = 0; \
    (ac_macro_this)->lfgc_next = 0; \
    (ac_macro_this)->fp_dtor = (ac_macro_fp_dtor); \
    (ac_macro_this)->state = (ac_macro_state)


    #define ac_i686_node_get_next( ac_macro_this ) \
    ( (ac_macro_this)->next )


    #define ac_i686_mb_node_get_next( ac_macro_this ) \
    ( (ac_i686_node_t*)ac_mb_loadptr_depends( &(ac_macro_this)->next ) )


    #define ac_i686_node_get_state( ac_macro_this ) \
    ( (void*)(ac_macro_this)->state )


    #define ac_i686_mb_node_get_state( ac_macro_this ) \
    ( ac_mb_loadptr_depends( &(ac_macro_this)->state ) )


    #define ac_i686_mb_loadptr_fence( ac_macro_state ) \
    ( (void*)ac_i686_mb_load_fence \
    ( (ac_i686_intword_t*)(ac_macro_state) ) )


    #define ac_i686_mb_loadptr_naked( ac_macro_state ) \
    ( (void*)ac_i686_mb_load_naked \
    ( (ac_i686_intword_t*)(ac_macro_state) ) )


    #define ac_i686_mb_storeptr_fence( ac_macro_dest, ac_macro_state ) \
    ( (void*)ac_i686_mb_store_fence \
    ( (ac_i686_intword_t*)(ac_macro_dest), \
    (ac_i686_intword_t)(ac_macro_state) ) )


    #define ac_i686_mb_storeptr_naked( ac_macro_dest, ac_macro_state ) \
    ( (void*)ac_i686_mb_store_naked \
    ( (ac_i686_intword_t*)(ac_macro_dest), \
    (ac_i686_intword_t)(ac_macro_state) ) )


    #define ac_i686_atomic_casptr_fence( ac_macro_dest, ac_macro_cmp, ac_macro_xchg ) \
    ( (void*)ac_i686_atomic_cas_fence \
    ( (ac_i686_intword_t*)(ac_macro_dest), \
    (ac_i686_intword_t)(ac_macro_cmp), \
    (ac_i686_intword_t)(ac_macro_xchg) ) )


    #define ac_i686_atomic_xchgptr_fence( ac_macro_dest, ac_macro_xchg ) \
    ( (void*)ac_i686_atomic_xchg_fence \
    ( (ac_i686_intword_t*)(ac_macro_dest), \
    (ac_i686_intword_t)(ac_macro_xchg) ) )








    /***** Low-Level Atomic API Abstraction *****/
    #define AC_CPU_CACHE_LINE AC_I686_CACHE_LINE
    #define AC_CPU_WORD_SIZE AC_I686_WORD_SIZE


    #ifndef AC_CPU_HAS_DWCAS
    #define AC_CPU_HAS_DWCAS
    #endif




    typedef ac_i686_intword_t ac_intword_t;
    typedef ac_i686_uintword_t ac_uintword_t;
    typedef ac_i686_intword2_t ac_intword2_t;
    typedef ac_i686_uintword2_t ac_uintword2_t;
    typedef ac_i686_flags_t ac_flags_t;


    typedef ac_i686_node_t ac_cpu_node_t;
    typedef ac_i686_stack_mpmc_t ac_cpu_stack_mpmc_t;
    typedef ac_i686_queue_spsc_t ac_cpu_queue_spsc_t;
    typedef ac_i686_tls_t ac_cpu_tls_t;




    #define ac_cpu_pause ac_i686_pause
    #define ac_cpu_pause_yield ac_i686_pause_yield


    #define ac_mb_fence ac_i686_mb_fence


    #define ac_mb_load_fence ac_i686_mb_load_fence
    #define ac_mb_load_naked ac_i686_mb_load_naked
    #define ac_mb_load_acquire ac_mb_load_naked
    #define ac_mb_load_depends ac_mb_load_naked
    #define ac_mb_loadptr_fence ac_i686_mb_loadptr_fence
    #define ac_mb_loadptr_naked ac_i686_mb_loadptr_naked
    #define ac_mb_loadptr_acquire ac_mb_loadptr_naked
    #define ac_mb_loadptr_depends ac_mb_loadptr_naked


    #define ac_mb_store_fence ac_i686_mb_store_fence
    #define ac_mb_store_naked ac_i686_mb_store_naked
    #define ac_mb_store_release ac_mb_store_naked
    #define ac_mb_storeptr_fence ac_i686_mb_storeptr_fence
    #define ac_mb_storeptr_naked ac_i686_mb_storeptr_naked
    #define ac_mb_storeptr_release ac_mb_storeptr_naked


    #define np_ac_atomic_dwcas_fence np_ac_i686_atomic_dwcas_fence
    #define np_ac_atomic_dwcas_acquire np_ac_atomic_dwcas_fence
    #define np_ac_atomic_dwcas_release np_ac_atomic_dwcas_fence
    #define np_ac_atomic_dwcas_depends np_ac_atomic_dwcas_fence


    #define ac_atomic_cas_fence ac_i686_atomic_cas_fence
    #define ac_atomic_cas_acquire ac_atomic_cas_fence
    #define ac_atomic_cas_release ac_atomic_cas_fence
    #define ac_atomic_cas_depends ac_atomic_cas_fence
    #define ac_atomic_casptr_fence ac_i686_atomic_casptr_fence
    #define ac_atomic_casptr_acquire ac_atomic_casptr_fence
    #define ac_atomic_casptr_release ac_atomic_casptr_fence
    #define ac_atomic_casptr_depends ac_atomic_casptr_fence


    #define ac_atomic_xadd_fence ac_i686_atomic_xadd_fence
    #define ac_atomic_xadd_acquire ac_atomic_xadd_fence
    #define ac_atomic_xadd_release ac_atomic_xadd_fence
    #define ac_atomic_xadd_depends ac_atomic_xadd_fence


    #define ac_atomic_xchg_fence ac_i686_atomic_xchg_fence
    #define ac_atomic_xchg_acquire ac_atomic_xchg_fence
    #define ac_atomic_xchg_release ac_atomic_xchg_fence
    #define ac_atomic_xchg_depends ac_atomic_xchg_fence
    #define ac_atomic_xchgptr_fence ac_i686_atomic_xchgptr_fence
    #define ac_atomic_xchgptr_acquire ac_atomic_xchgptr_fence
    #define ac_atomic_xchgptr_release ac_atomic_xchgptr_fence
    #define ac_atomic_xchgptr_depends ac_atomic_xchgptr_fence


    #define ac_atomic_inc_fence ac_i686_atomic_inc_fence
    #define ac_atomic_inc_acquire ac_atomic_inc_fence
    #define ac_atomic_inc_release ac_atomic_inc_fence
    #define ac_atomic_inc_depends ac_atomic_inc_fence


    #define ac_atomic_dec_fence ac_i686_atomic_dec_fence
    #define ac_atomic_dec_acquire ac_atomic_dec_fence
    #define ac_atomic_dec_release ac_atomic_dec_fence
    #define ac_atomic_dec_depends ac_atomic_dec_fence


    #define ac_cpu_queue_spsc_init ac_i686_queue_spsc_init
    #define ac_cpu_queue_spsc_push ac_i686_queue_spsc_push
    #define ac_cpu_queue_spsc_pop ac_i686_queue_spsc_pop


    #define ac_cpu_stack_mpmc_init ac_i686_stack_mpmc_init
    #define ac_cpu_stack_mpmc_push_cas ac_i686_stack_mpmc_push_cas
    #define np_ac_cpu_stack_mpmc_pop_dwcas np_ac_i686_stack_mpmc_pop_dwcas


    #define ac_cpu_node_init ac_i686_node_init
    #define ac_cpu_node_get_next ac_i686_node_get_next
    #define ac_cpu_mb_node_get_next ac_i686_mb_node_get_next
    #define ac_cpu_node_get_state ac_i686_node_get_state
    #define ac_cpu_mb_node_get_state ac_i686_mb_node_get_state
    #define ac_cpu_node_cache_pop ac_i686_node_cache_pop
    #define ac_cpu_node_cache_push ac_i686_node_cache_push
    #define ac_cpu_node_cache_push_no_free ac_i686_node_cache_push_no_free





    #ifdef __cplusplus
    }
    #endif


    #endif




    #include "ac_i686_lfgc_smr.h"
    #include "ac_i686_lfgc_refcount.h"


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Sep 24 15:31:46 2026
    On 9/21/2026 8:47 AM, boltar@caprica.universe wrote:
    On Mon, 21 Sep 2026 14:50:19 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good
    ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course,

    Unless the assembler is just a few opcodes you're better off leaving it to
    a compiler as it'll usually do a better job and probably knows opcodes you've
    never even heard of.





    Back then, I was really worried because the C/C++ stds did not give a
    shit about atomics. Well, those that claimed POSIX std support aside for
    a moment:

    https://web.archive.org/web/20071008213108/http://appcore.home.comcast.net/

    AppCore: A Portable High-Performance Thread Synchronization Library

    An Effective Marriage between Lock-Free and Lock-Based Algorithms


    This page presents a scaleable single-producer/consumer lock-free queue
    and an atomic operations api for the i686. There is also a full-blown
    hazard pointer implementation that is used to create an atomic reference
    count api. This makes it possible to create a single-word atomic C++
    smart pointer. A very crude smart pointer class is included as a proof
    of concept. The code is made up of bits and pieces from my ?new and
    improved? AppCore Library. I can't reveal the entire library until I completely finish its documentation. However, the presented algorithm
    provides a highly-efficient method for thread-to-thread communication.
    The code compiles with gcc or msvc++ and relies on pthreads; no
    dependence on windows headers. It should compile with gcc on many
    operating systems that provide pthreads under i686 cpu's. The lock-free
    queue does not rely on any ?atomic operations?. It uses simple loads and stores guarded with memory barriers. All of its ?critical-sequences? are contained in externally assembled functions ( read all ) in order to
    prevent a rouge C compiler from reordering anything that would corrupt
    the data-structure. The queue allocates its nodes from a three-level
    cache. The first level is a local per-thread LIFO, the second is a
    global lock-free LIFO, and the third is malloc/free. The lock-free data-structures are padded and forcefully aligned on separate cache
    lines. Unfortunately, all of this is necessary to achieve scaleable thread-to-thread communication. Test the presented code, and compare and contrast its performance against a "traditional lock-based" solution for
    a single-producer/consumer queue. You just may find that lock-free thread-to-thread communication can be stable, simple, and "useful" after all... ;)


    ****This site is under construction****



    Contact
    Remove nospam and underscores: nospam_cristom@nospam_comcast.net
    I also post and discuss information on AppCore to comp.programming.threads


    Links
    A Fast-Pathed POSIX Thread library

    "Mostly" Lock-Free Word-Based Atomic Reference Counting (source)


    Code Status
    - Completed: Feb. 21, 2005: Added atomic api
    - Completed: Feb. 21, 2005: Critical update! Slays simple mem leak wrt
    tls caused by stupid cut&paste; error! ;(...
    - Completed: Mar. 14, 2005: Added hazard-pointers
    - Completed: Mar. 25, 2005: Critical update! Added updated assembly
    files. Re-download
    - Completed: Mar. 30, 2005: Added simple MSVC 6.0 workspaces and Dev-C++ project files
    - Completed: Apr. 18, 2005: Fixed pthread related memory leak that only
    shows up on Linux. Re-download
    - Completed: May 2, 2005: Minor code updates and other various
    performance enhancements. Re-download
    - Completed: May 3, 2005: Found and removed a small bug wrt SMR & node
    cache. It?s hard to trip, but it will crash if it does. Its fixed so you should probably re-download.
    - Completed: May 10, 2005: Found and removed the last SMR & node cache
    related bug. Again, it can be hard to trip. Re-download
    - Completed: May 14, 2005: Found and removed alloca scoping bug.
    - Currently: Verifying that AppCore is now bug free! :)


    Downloads
    appcore version: 0.0.1 / arch: i686 ( pre-alpha )
    The MSVC 6.0 workspaces and the Dev-C++ projects build the appcore.dll
    in the c:/winnt/system32 directory. This may need to be changed to suite
    your needs.


    Project File Index(s)
    appcore project

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Sep 25 08:33:44 2026
    On 25/09/2026 00:28, Chris M. Thomasson wrote:
    On 9/21/2026 5:50 AM, David Brown wrote:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good
    ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course, [...]

    Yeah. Well. Shit. Back then I did not want any compiler to mess with
    things, and my inline skills were rather lacking! I felt way better
    making a header the structs, and externally assembling things. Keep in
    mind this was before C/C++ 11. I even said woe to LTO.


    I was not trying to show pre-C++11 Chris how to write inline assembly
    for atomics. I was trying to show 2026 Chris how to write inline
    assembly /today/, because it is still useful for some things (like a
    16-byte compare-and-swap). I was also trying to show Joseph how to do a better job than he had so far. (Again, it's on the inline assembly
    part, not the choice of instructions or atomic handling.)

    You didn't know the details of gcc inline assembly back then - fair
    enough, it's not easy, and has subtly challenges. Now you know a bit
    more going forward. And I really do think it is something that you
    would enjoy playing with.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Sep 25 18:25:41 2026
    Am 21.09.2026 um 18:45 schrieb David Brown:

    The point here was an opcode - the 16 byte compare-and-exchange - that
    the compiler does /not/ know about, and is not supported by the C++ (or
    C) standard library for atomics.

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Sep 25 18:40:14 2026
    On 25/09/2026 18:25, Bonita Montero wrote:
    Am 21.09.2026 um 18:45 schrieb David Brown:

    The point here was an opcode - the 16 byte compare-and-exchange - that
    the compiler does /not/ know about, and is not supported by the C++
    (or C) standard library for atomics.

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.

    My (admittedly very brief) testing suggested gcc calls a library
    function, rather than using the specific opcode directly in generated
    code. Perhaps this is affected by flags selecting the specific
    architecture. I neither know nor care particularly - that is beside the point. I am not interested in using these instructions, and leave that
    to the posters who want to implement RCU or other lock-free algorithms.

    Of course there are good reasons for using standard library methods for
    this kind of thing, rather than inline assembly, even if that results in slower code. But for those that have use of assembly, the post I wrote
    shows how it can be done for these example instructions, in a manner
    that is safer, more efficient and more flexible than the solutions
    provided by Joseph or Chris.

    I assume that they both know what is and is not supported by gcc and its current C++ standard library.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Fri Sep 25 13:47:59 2026
    On 9/25/26 12:25 PM, Bonita Montero wrote:
    Am 21.09.2026 um 18:45 schrieb David Brown:

    The point here was an opcode - the 16 byte compare-and-exchange - that
    the compiler does /not/ know about, and is not supported by the C++
    (or C) standard library for atomics.

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.

    You need some way to avoid the ABA problem.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Sep 25 20:54:32 2026
    On 25/09/2026 19:47, Joseph Seigh wrote:
    On 9/25/26 12:25 PM, Bonita Montero wrote:
    Am 21.09.2026 um 18:45 schrieb David Brown:

    The point here was an opcode - the 16 byte compare-and-exchange -
    that the compiler does /not/ know about, and is not supported by the
    C++ (or C) standard library for atomics.

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
    inline-assembly.

    You need some way to avoid the ABA problem.


    That's entirely possible - and I expect you have more experience in that
    than I do. I have no experience with this on x86 - I work in microcontrollers, which are often simpler for such things. I haven't considered anything other than implementing the instruction - issues
    like ABA are no different when you use optimal inline assembly, less
    optimal inline assembly, or external assembly.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Fri Sep 25 17:20:43 2026
    On 9/25/26 2:54 PM, David Brown wrote:
    On 25/09/2026 19:47, Joseph Seigh wrote:
    On 9/25/26 12:25 PM, Bonita Montero wrote:

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
    inline-assembly.

    You need some way to avoid the ABA problem.


    That's entirely possible - and I expect you have more experience in that than I do.˙ I have no experience with this on x86 - I work in microcontrollers, which are often simpler for such things.˙ I haven't considered anything other than implementing the instruction - issues
    like ABA are no different when you use optimal inline assembly, less
    optimal inline assembly, or external assembly.


    The 16 byte version lets you combine a memory reference w/ a number as a
    fat pointer to let you distinguish distinguish between two instances
    of an object sharing the same memory address (due to the memory being reallocated).* Basically the ABA problem.

    The example I posted is from a lock-free ring buffer, a bounded queue.
    Most of the lock-free ring buffers you see out there aren't really
    lock-free and/or an actual queue unless you qualify with "for some
    definition of lock-free" and "for some definition of queue".

    For linked queues, you have the Michael-Scott lock-free queue where
    you can get away with a single wide CAS but you need something like
    hazard pointers or RCU to avoid the ABA problem and to avoid doing
    CAS on a memory location with undefined state. LL/SC doesn't have
    this problem however.

    Double wide CAS support would be nice to have, but I'm not holding
    my breath.

    * In the 70's a double wide CAS on an IBM mainframe was 64 bits.
    Using a 32 bit number and 32 bit address, it was estimated that
    a 32 bit number would take about 100 years to wrap on the
    current hardware then.





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Sep 26 16:23:37 2026
    On 25/09/2026 23:20, Joseph Seigh wrote:
    On 9/25/26 2:54 PM, David Brown wrote:
    On 25/09/2026 19:47, Joseph Seigh wrote:
    On 9/25/26 12:25 PM, Bonita Montero wrote:

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
    inline-assembly.

    You need some way to avoid the ABA problem.


    That's entirely possible - and I expect you have more experience in
    that than I do.˙ I have no experience with this on x86 - I work in
    microcontrollers, which are often simpler for such things.˙ I haven't
    considered anything other than implementing the instruction - issues
    like ABA are no different when you use optimal inline assembly, less
    optimal inline assembly, or external assembly.


    The 16 byte version lets you combine a memory reference w/ a number as a
    fat pointer to let you distinguish distinguish between two instances
    of an object sharing the same memory address (due to the memory being reallocated).*˙ Basically the ABA problem.


    That fits my understand, which is nice to see.

    The example I posted is from a lock-free ring buffer, a bounded queue.
    Most of the lock-free ring buffers you see out there aren't really
    lock-free and/or an actual queue unless you qualify with "for some
    definition of lock-free" and "for some definition of queue".

    For linked queues, you have the Michael-Scott lock-free queue where
    you can get away with a single wide CAS but you need something like
    hazard pointers or RCU to avoid the ABA problem and to avoid doing
    CAS on a memory location with undefined state.˙ LL/SC doesn't have
    this problem however.

    Double wide CAS support would be nice to have, but I'm not holding
    my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?

    * In the 70's a double wide CAS on an IBM mainframe was 64 bits.
    Using a 32 bit number and 32 bit address, it was estimated that
    a 32 bit number would take about 100 years to wrap on the
    current hardware then.






    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Sat Sep 26 15:36:59 2026
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding
    my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++. There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all. The way they would like to is an atomic<T> where T would
    be an 8 byte type. But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.

    I have a pre c++11 implementation of atomic reference counting
    (actually atomic, not like Rust's ARC which is merely thread-safe)
    and there's no reason to rewrite it post c++11. I'd still need
    assembly.

    Anyway, there's solutions to more interesting concurrency problems to
    consider.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Sep 27 14:56:21 2026
    On 26/09/2026 21:36, Joseph Seigh wrote:
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding
    my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++.˙ There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all.˙ The way they would like to is an atomic<T> where T would
    be an 8 byte type.˙ But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.


    Okay.

    There is a strong reluctance to put things into the standards if they
    can't be realistically and practically implemented on most platforms.

    But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
    size at all. And it supports compare_exchange() methods (strong and
    weak) on those types.

    As far as I can see, it's up to the implementation how those are
    handled. gcc on x86-64 does so with a library call "__atomic_compare_exchange_16". (Bigger sizes use a more general __atomic_compare_exchange call.) If the target supports a dedicated instruction here, and the instruction is more efficient than the library
    call (that's usually the case!), then this is just a compiler quality of implementation issue. When someone adds this to the compiler and/or gcc
    C++ standard library implementation, it should just work when
    appropriate target selection flags are used.


    I have a pre c++11 implementation of atomic reference counting
    (actually atomic, not like Rust's ARC which is merely thread-safe)
    and there's no reason to rewrite it post c++11.˙ I'd still need
    assembly.

    Anyway, there's solutions to more interesting concurrency problems to consider.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Sep 28 15:42:13 2026
    On 9/24/2026 11:33 PM, David Brown wrote:
    On 25/09/2026 00:28, Chris M. Thomasson wrote:
    On 9/21/2026 5:50 AM, David Brown wrote:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>>> sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good
    ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course, [...]

    Yeah. Well. Shit. Back then I did not want any compiler to mess with
    things, and my inline skills were rather lacking! I felt way better
    making a header the structs, and externally assembling things. Keep in
    mind this was before C/C++ 11. I even said woe to LTO.


    I was not trying to show pre-C++11 Chris how to write inline assembly
    for atomics.˙ I was trying to show 2026 Chris how to write inline
    assembly /today/, because it is still useful for some things (like a 16- byte compare-and-swap).˙ I was also trying to show Joseph how to do a
    better job than he had so far.˙ (Again, it's on the inline assembly
    part, not the choice of instructions or atomic handling.)

    You didn't know the details of gcc inline assembly back then - fair
    enough, it's not easy, and has subtly challenges.˙ Now you know a bit
    more going forward.˙ And I really do think it is something that you
    would enjoy playing with.


    Fwiw, I felt way better with externally assembled ASM back then. The
    syntax for GCC inline was/is a bit "bitter" to me, MASM was a little
    better, alas. But, I understand your main point.

    I still want to put it into a separate file, GAS it into an .o file and
    link the little shit.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Sep 28 15:44:43 2026
    On 9/25/2026 2:20 PM, Joseph Seigh wrote:
    On 9/25/26 2:54 PM, David Brown wrote:
    On 25/09/2026 19:47, Joseph Seigh wrote:
    On 9/25/26 12:25 PM, Bonita Montero wrote:

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
    inline-assembly.

    You need some way to avoid the ABA problem.


    That's entirely possible - and I expect you have more experience in
    that than I do.˙ I have no experience with this on x86 - I work in
    microcontrollers, which are often simpler for such things.˙ I haven't
    considered anything other than implementing the instruction - issues
    like ABA are no different when you use optimal inline assembly, less
    optimal inline assembly, or external assembly.


    The 16 byte version lets you combine a memory reference w/ a number as a
    fat pointer to let you distinguish distinguish between two instances
    of an object sharing the same memory address (due to the memory being reallocated).*˙ Basically the ABA problem.

    The example I posted is from a lock-free ring buffer, a bounded queue.
    Most of the lock-free ring buffers you see out there aren't really
    lock-free and/or an actual queue unless you qualify with "for some
    definition of lock-free" and "for some definition of queue".

    For linked queues, you have the Michael-Scott lock-free queue where
    you can get away with a single wide CAS but you need something like
    hazard pointers or RCU to avoid the ABA problem and to avoid doing
    CAS on a memory location with undefined state.˙ LL/SC doesn't have
    this problem however.

    Double wide CAS support would be nice to have, but I'm not holding
    my breath.

    Last time I checked god damn C++ said it was not lockfree wrt two
    contiguous words! So, I see why you are still using asm.



    * In the 70's a double wide CAS on an IBM mainframe was 64 bits.
    Using a 32 bit number and 32 bit address, it was estimated that
    a 32 bit number would take about 100 years to wrap on the
    current hardware then.






    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Sep 28 15:45:38 2026
    On 9/25/2026 9:25 AM, Bonita Montero wrote:
    Am 21.09.2026 um 18:45 schrieb David Brown:

    The point here was an opcode - the 16 byte compare-and-exchange - that
    the compiler does /not/ know about, and is not supported by the C++
    (or C) standard library for atomics.

    Use atomic_ref with a trivial structure that is 16 bytes with x64 or
    8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
    With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.

    Did they finally make it use the DWCAS and say its always lock-free?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Sep 28 15:50:53 2026
    On 9/27/2026 5:56 AM, David Brown wrote:
    On 26/09/2026 21:36, Joseph Seigh wrote:
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding
    my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++.˙ There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all.˙ The way they would like to is an atomic<T> where T would
    be an 8 byte type.˙ But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.


    Okay.

    There is a strong reluctance to put things into the standards if they
    can't be realistically and practically implemented on most platforms.

    But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
    size at all.˙ And it supports compare_exchange() methods (strong and
    weak) on those types.

    As far as I can see, it's up to the implementation how those are
    handled.˙ gcc on x86-64 does so with a library call "__atomic_compare_exchange_16".˙ (Bigger sizes use a more general __atomic_compare_exchange call.)˙ If the target supports a dedicated instruction here, and the instruction is more efficient than the library call (that's usually the case!), then this is just a compiler quality of implementation issue.˙ When someone adds this to the compiler and/or gcc
    C++ standard library implementation, it should just work when
    appropriate target selection flags are used.

    It is a QOI, as far as I can tell. Checked it a while ago, and it says
    that the DWCAS is not lock free. That scares me. If its not lock free,
    then there is no reason to use it for these types of things. ;^o





    I have a pre c++11 implementation of atomic reference counting
    (actually atomic, not like Rust's ARC which is merely thread-safe)
    and there's no reason to rewrite it post c++11.˙ I'd still need
    assembly.

    Anyway, there's solutions to more interesting concurrency problems to
    consider.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Sep 29 08:31:08 2026
    On 29/09/2026 00:50, Chris M. Thomasson wrote:
    On 9/27/2026 5:56 AM, David Brown wrote:
    On 26/09/2026 21:36, Joseph Seigh wrote:
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding
    my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++.˙ There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all.˙ The way they would like to is an atomic<T> where T would
    be an 8 byte type.˙ But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.


    Okay.

    There is a strong reluctance to put things into the standards if they
    can't be realistically and practically implemented on most platforms.

    But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
    size at all.˙ And it supports compare_exchange() methods (strong and
    weak) on those types.

    As far as I can see, it's up to the implementation how those are
    handled.˙ gcc on x86-64 does so with a library call
    "__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
    __atomic_compare_exchange call.)˙ If the target supports a dedicated
    instruction here, and the instruction is more efficient than the
    library call (that's usually the case!), then this is just a compiler
    quality of implementation issue.˙ When someone adds this to the
    compiler and/or gcc C++ standard library implementation, it should
    just work when appropriate target selection flags are used.

    It is a QOI, as far as I can tell. Checked it a while ago, and it says
    that the DWCAS is not lock free. That scares me. If its not lock free,
    then there is no reason to use it for these types of things. ;^o


    The answer then would be to file an issue with the gcc bugzilla (since
    it's their C++ standard library implementation you are looking at).
    Feel free to post my inline assembly there in the issue, and post the
    issue link here.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Sep 29 08:40:31 2026
    On 29/09/2026 00:42, Chris M. Thomasson wrote:
    On 9/24/2026 11:33 PM, David Brown wrote:
    On 25/09/2026 00:28, Chris M. Thomasson wrote:
    On 9/21/2026 5:50 AM, David Brown wrote:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-
    concurrent- sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used good >>>>> ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course, [...]

    Yeah. Well. Shit. Back then I did not want any compiler to mess with
    things, and my inline skills were rather lacking! I felt way better
    making a header the structs, and externally assembling things. Keep
    in mind this was before C/C++ 11. I even said woe to LTO.


    I was not trying to show pre-C++11 Chris how to write inline assembly
    for atomics.˙ I was trying to show 2026 Chris how to write inline
    assembly /today/, because it is still useful for some things (like a
    16- byte compare-and-swap).˙ I was also trying to show Joseph how to
    do a better job than he had so far.˙ (Again, it's on the inline
    assembly part, not the choice of instructions or atomic handling.)

    You didn't know the details of gcc inline assembly back then - fair
    enough, it's not easy, and has subtly challenges.˙ Now you know a bit
    more going forward.˙ And I really do think it is something that you
    would enjoy playing with.


    Fwiw, I felt way better with externally assembled ASM back then. The
    syntax for GCC inline was/is a bit "bitter" to me, MASM was a little
    better, alas. But, I understand your main point.

    I still want to put it into a separate file, GAS it into an .o file and
    link the little shit.

    External assembly can be better for some types of code, but it is
    definitely inappropriate here. With external assembly you have to stick rigidly to the ABI for external calls, but it is clearer for large
    blocks of assembly (or other occasionally useful advanced stuff).
    However, the only reason you need large blocks of assembly for wrapping
    a single instruction is because you are using external assembly and the
    ABI for function calls! Inline assembly not only avoids that overhead,
    but lets the compiler integrate it much better with the rest of the code.

    The whole point of using lock-free algorithms here, and the double compare-and-swap, is speed. If you use external assembly so that all
    your important data has to be pushed from registers to the stack, then
    you have a call, then you pull the data off the stack again - you've a
    dozen extra instructions at the call site, a dozen extra instructions at
    the cas16 site, doing nothing except spoiling your efficient code.

    gcc inline assembly is a /much/ better solution here. It's okay if you
    don't know how to write it - and it is certainly okay if you did not
    know how to do so a decade ago. But you should understand why it is
    better, and then decide if you are going to learn about it.

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)






    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Sep 29 16:30:31 2026
    On 9/28/2026 11:40 PM, David Brown wrote:
    On 29/09/2026 00:42, Chris M. Thomasson wrote:
    On 9/24/2026 11:33 PM, David Brown wrote:
    On 25/09/2026 00:28, Chris M. Thomasson wrote:
    On 9/21/2026 5:50 AM, David Brown wrote:
    On 21/09/2026 01:09, Chris M. Thomasson wrote:
    On 9/20/2026 7:58 AM, Joseph Seigh wrote:
    On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
    On 9/16/2026 3:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-
    concurrent- sequential-rcu/


    C++ is sort of in the same boat, which is why we still need to
    use inline assembly to implement half century old algorithms
    efficiently.

    ˙˙never really got into inline asm. Well. I said f it and used
    good ol GAS. Or MASM.


    You really should learn about inline assembly in gcc.˙ It can be
    significantly more efficient than using an external call in a
    separately assembled file.˙ And I am confident that you'd find it
    fun!˙ (Of course, [...]

    Yeah. Well. Shit. Back then I did not want any compiler to mess with
    things, and my inline skills were rather lacking! I felt way better
    making a header the structs, and externally assembling things. Keep
    in mind this was before C/C++ 11. I even said woe to LTO.


    I was not trying to show pre-C++11 Chris how to write inline assembly
    for atomics.˙ I was trying to show 2026 Chris how to write inline
    assembly /today/, because it is still useful for some things (like a
    16- byte compare-and-swap).˙ I was also trying to show Joseph how to
    do a better job than he had so far.˙ (Again, it's on the inline
    assembly part, not the choice of instructions or atomic handling.)

    You didn't know the details of gcc inline assembly back then - fair
    enough, it's not easy, and has subtly challenges.˙ Now you know a bit
    more going forward.˙ And I really do think it is something that you
    would enjoy playing with.


    Fwiw, I felt way better with externally assembled ASM back then. The
    syntax for GCC inline was/is a bit "bitter" to me, MASM was a little
    better, alas. But, I understand your main point.

    I still want to put it into a separate file, GAS it into an .o file
    and link the little shit.

    External assembly can be better for some types of code, but it is
    definitely inappropriate here.˙ With external assembly you have to stick rigidly to the ABI for external calls, but it is clearer for large
    blocks of assembly (or other occasionally useful advanced stuff).
    However, the only reason you need large blocks of assembly for wrapping
    a single instruction is because you are using external assembly and the
    ABI for function calls!˙ Inline assembly not only avoids that overhead,
    but lets the compiler integrate it much better with the rest of the code.

    The whole point of using lock-free algorithms here, and the double compare-and-swap, is speed.˙ If you use external assembly so that all
    your important data has to be pushed from registers to the stack, then
    you have a call, then you pull the data off the stack again - you've a
    dozen extra instructions at the call site, a dozen extra instructions at
    the cas16 site, doing nothing except spoiling your efficient code.

    gcc inline assembly is a /much/ better solution here.˙ It's okay if you don't know how to write it - and it is certainly okay if you did not
    know how to do so a decade ago.˙ But you should understand why it is
    better, and then decide if you are going to learn about it.

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
    Sigh. Clobbers what? When and where? cdecl for the ABI and externally assembled functions are fine for me, LTO aside for a moment, can be bad
    do not go into my asm! BUT. Again. Shit. Its a personal problem wrt me
    saying inline bad! Sorry!

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
    64 bit words on a 64 bit machine, well GOOD! Show me. I have had some experiences with it trying to tell me its not lock free.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Sep 30 09:54:35 2026
    On 30/09/2026 01:30, Chris M. Thomasson wrote:
    On 9/28/2026 11:40 PM, David Brown wrote:

    gcc inline assembly is a /much/ better solution here.˙ It's okay if
    you don't know how to write it - and it is certainly okay if you did
    not know how to do so a decade ago.˙ But you should understand why it
    is better, and then decide if you are going to learn about it.

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
    Sigh. Clobbers what? When and where? cdecl for the ABI and externally assembled functions are fine for me, LTO aside for a moment, can be bad
    do not go into my asm! BUT. Again. Shit. Its a personal problem wrt me saying inline bad! Sorry!


    Not knowing the syntax for gcc inline assembly is not a "problem" as
    such - most C programmers never need it. It is just a strong
    recommendation that /if/ you want to write assembly code, then your
    results would be much improved by using inline assembly format rather
    than external assembly functions. And you are already familiar with the
    hard part, the actual assembly. So don't look at this as a problem of
    any sort - look at it as a fun, challenging and useful new direction for
    your existing skills and interests.

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
    64 bit words on a 64 bit machine, well GOOD! Show me. I have had some experiences with it trying to tell me its not lock free.

    The C++ standard library supports 16 byte compare-and-exchange. The gcc implementation of that library does not use that instruction, and thus
    does not support lock-free 16 byte compare-and-exchange. C++ has
    supported this since C++11 - this is a QOI issue, not a language or
    standard library issue. File an issue on the gcc bugzilla - that's how
    you get progress (or how we will learn if there are good reasons not to
    use it.)


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Oct 2 11:11:24 2026
    On 9/30/2026 12:54 AM, David Brown wrote:
    On 30/09/2026 01:30, Chris M. Thomasson wrote:
    On 9/28/2026 11:40 PM, David Brown wrote:

    gcc inline assembly is a /much/ better solution here.˙ It's okay if
    you don't know how to write it - and it is certainly okay if you did
    not know how to do so a decade ago.˙ But you should understand why it
    is better, and then decide if you are going to learn about it.

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own
    personal problem with the syntax of GCC inline asm. I am a bit scared
    of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
    externally assembled functions are fine for me, LTO aside for a
    moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
    personal problem wrt me saying inline bad! Sorry!


    Not knowing the syntax for gcc inline assembly is not a "problem" as
    such - most C programmers never need it.˙ It is just a strong
    recommendation that /if/ you want to write assembly code, then your
    results would be much improved by using inline assembly format rather
    than external assembly functions.˙ And you are already familiar with the hard part, the actual assembly.˙ So don't look at this as a problem of
    any sort - look at it as a fun, challenging and useful new direction for your existing skills and interests.

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
    64 bit words on a 64 bit machine, well GOOD! Show me. I have had some
    experiences with it trying to tell me its not lock free.

    The C++ standard library supports 16 byte compare-and-exchange.˙ The gcc implementation of that library does not use that instruction, and thus
    does not support lock-free 16 byte compare-and-exchange.˙ C++ has
    supported this since C++11 - this is a QOI issue, not a language or
    standard library issue.˙ File an issue on the gcc bugzilla - that's how
    you get progress (or how we will learn if there are good reasons not to
    use it.)


    I first need to show why its a qoi to begin with. If the arch supports a single instruction for a double word atomic rmw op (DWCAS), it should be
    using that instruction. And! it needs to report it as always lock-free.
    Then the user has all the info they need for a fallback to asm. Then
    show a bunch of examples where its needed. But that should not be all
    that hard. Say okay, if the C/C++ does not make this atomic RMW
    lock-free on an arch that supports it, then its useless for anything
    lock-free on said arch. ASM to the rescue?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Oct 2 11:13:23 2026
    On 9/28/2026 11:31 PM, David Brown wrote:
    On 29/09/2026 00:50, Chris M. Thomasson wrote:
    On 9/27/2026 5:56 AM, David Brown wrote:
    On 26/09/2026 21:36, Joseph Seigh wrote:
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding >>>>>> my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++.˙ There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all.˙ The way they would like to is an atomic<T> where T would
    be an 8 byte type.˙ But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.


    Okay.

    There is a strong reluctance to put things into the standards if they
    can't be realistically and practically implemented on most platforms.

    But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
    size at all.˙ And it supports compare_exchange() methods (strong and
    weak) on those types.

    As far as I can see, it's up to the implementation how those are
    handled.˙ gcc on x86-64 does so with a library call
    "__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
    __atomic_compare_exchange call.)˙ If the target supports a dedicated
    instruction here, and the instruction is more efficient than the
    library call (that's usually the case!), then this is just a compiler
    quality of implementation issue.˙ When someone adds this to the
    compiler and/or gcc C++ standard library implementation, it should
    just work when appropriate target selection flags are used.

    It is a QOI, as far as I can tell. Checked it a while ago, and it says
    that the DWCAS is not lock free. That scares me. If its not lock free,
    then there is no reason to use it for these types of things. ;^o


    The answer then would be to file an issue with the gcc bugzilla (since
    it's their C++ standard library implementation you are looking at). Feel free to post my inline assembly there in the issue, and post the issue
    link here.


    It should be something akin to, why do I feel the need to code this is
    ASM when the C/C++11 should already do it for me. That type of angle?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Fri Oct 2 14:39:34 2026
    On 9/29/26 7:30 PM, Chris M. Thomasson wrote:
    On 9/28/2026 11:40 PM, David Brown wrote:
    On 29/09/2026 00:42, Chris M. Thomasson wrote:
    ...

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
    Sigh. Clobbers what? When and where? cdecl for the ABI and externally assembled functions are fine for me, LTO aside for a moment, can be bad
    do not go into my asm! BUT. Again. Shit. Its a personal problem wrt me saying inline bad! Sorry!

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
    64 bit words on a 64 bit machine, well GOOD! Show me. I have had some experiences with it trying to tell me its not lock free.

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878

    The rationale seems to be you can mix a lock-free operation
    with locked operations since __atomic_always_lock_free and __atomic_is_lock_free apply to the data type not specific
    builtins.

    Given that there seems to be no shortage of x86 specific
    gcc builtins, I can't think of any reason why there's
    no cmpxchg16b builtin.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Oct 3 14:19:56 2026
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    On 9/28/2026 11:31 PM, David Brown wrote:
    On 29/09/2026 00:50, Chris M. Thomasson wrote:
    On 9/27/2026 5:56 AM, David Brown wrote:
    On 26/09/2026 21:36, Joseph Seigh wrote:
    On 9/26/26 10:23 AM, David Brown wrote:
    On 25/09/2026 23:20, Joseph Seigh wrote:


    Double wide CAS support would be nice to have, but I'm not holding >>>>>>> my breath.


    Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?


    I meant as part of c/c++.˙ There really no legitimate technical
    reason it's not in the standard now, it's just they can't put
    it in the way they would like too, so they refuse to put it in
    at all.˙ The way they would like to is an atomic<T> where T would
    be an 8 byte type.˙ But they can't since there are no atomic 8 byte
    load and store instructions on most existing x86-64 processors
    and that's the only way they are willing to do it.


    Okay.

    There is a strong reluctance to put things into the standards if
    they can't be realistically and practically implemented on most
    platforms.

    But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
    size at all.˙ And it supports compare_exchange() methods (strong and
    weak) on those types.

    As far as I can see, it's up to the implementation how those are
    handled.˙ gcc on x86-64 does so with a library call
    "__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
    __atomic_compare_exchange call.)˙ If the target supports a dedicated
    instruction here, and the instruction is more efficient than the
    library call (that's usually the case!), then this is just a
    compiler quality of implementation issue.˙ When someone adds this to
    the compiler and/or gcc C++ standard library implementation, it
    should just work when appropriate target selection flags are used.

    It is a QOI, as far as I can tell. Checked it a while ago, and it
    says that the DWCAS is not lock free. That scares me. If its not lock
    free, then there is no reason to use it for these types of things. ;^o


    The answer then would be to file an issue with the gcc bugzilla (since
    it's their C++ standard library implementation you are looking at).
    Feel free to post my inline assembly there in the issue, and post the
    issue link here.


    It should be something akin to, why do I feel the need to code this is
    ASM when the C/C++11 should already do it for me. That type of angle?

    Something along these lines should be fine. There may be some
    underlying reason why this is implemented as a library call in gcc's C++ standard library, rather than a direct lock-free opcode. I don't know
    the details of any of this - maybe there are flaws in some chips, maybe
    the single op-code actually takes longer than a lock-based
    implementation. Maybe it's just that no one has got round to
    implementing it yet.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Oct 3 14:22:18 2026
    On 02/10/2026 20:11, Chris M. Thomasson wrote:
    On 9/30/2026 12:54 AM, David Brown wrote:
    On 30/09/2026 01:30, Chris M. Thomasson wrote:
    On 9/28/2026 11:40 PM, David Brown wrote:

    gcc inline assembly is a /much/ better solution here.˙ It's okay if
    you don't know how to write it - and it is certainly okay if you did
    not know how to do so a decade ago.˙ But you should understand why
    it is better, and then decide if you are going to learn about it.

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own
    personal problem with the syntax of GCC inline asm. I am a bit scared
    of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
    externally assembled functions are fine for me, LTO aside for a
    moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
    personal problem wrt me saying inline bad! Sorry!


    Not knowing the syntax for gcc inline assembly is not a "problem" as
    such - most C programmers never need it.˙ It is just a strong
    recommendation that /if/ you want to write assembly code, then your
    results would be much improved by using inline assembly format rather
    than external assembly functions.˙ And you are already familiar with
    the hard part, the actual assembly.˙ So don't look at this as a
    problem of any sort - look at it as a fun, challenging and useful new
    direction for your existing skills and interests.

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two
    contiguous 64 bit words on a 64 bit machine, well GOOD! Show me. I
    have had some experiences with it trying to tell me its not lock free.

    The C++ standard library supports 16 byte compare-and-exchange.˙ The
    gcc implementation of that library does not use that instruction, and
    thus does not support lock-free 16 byte compare-and-exchange.˙ C++ has
    supported this since C++11 - this is a QOI issue, not a language or
    standard library issue.˙ File an issue on the gcc bugzilla - that's
    how you get progress (or how we will learn if there are good reasons
    not to use it.)


    I first need to show why its a qoi to begin with. If the arch supports a single instruction for a double word atomic rmw op (DWCAS), it should be using that instruction. And! it needs to report it as always lock-free.
    Then the user has all the info they need for a fallback to asm. Then
    show a bunch of examples where its needed. But that should not be all
    that hard. Say okay, if the C/C++ does not make this atomic RMW lock-
    free on an arch that supports it, then its useless for anything lock-
    free on said arch. ASM to the rescue?

    The inline asm certainly gives you a way to get efficient use of the instruction before it is in the toolchain implementation. But the
    toolchain implementation is the best long-term solution. So do both -
    use the inline asm yourself while encouraging gcc to implement it in
    their library for the future.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Sat Oct 3 15:50:13 2026
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code this is
    ASM when the C/C++11 should already do it for me. That type of angle?

    Something along these lines should be fine. There may be some
    underlying reason why this is implemented as a library call in gcc's C++ >standard library, rather than a direct lock-free opcode. I don't know
    the details of any of this - maybe there are flaws in some chips, maybe
    the single op-code actually takes longer than a lock-based
    implementation. Maybe it's just that no one has got round to
    implementing it yet.

    I would guess the reason is consistent behaviour across different architectures.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Oct 3 14:12:11 2026
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code this
    is ASM when the C/C++11 should already do it for me. That type of angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in gcc's
    C++ standard library, rather than a direct lock-free opcode.˙ I don't
    know the details of any of this - maybe there are flaws in some chips,
    maybe the single op-code actually takes longer than a lock-based
    implementation.˙ Maybe it's just that no one has got round to
    implementing it yet.

    I would guess the reason is consistent behaviour across different architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need it
    for then that would be okay. We need to step into the library call to
    find out.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Oct 4 11:34:29 2026
    On 03/10/2026 23:12, Chris M. Thomasson wrote:
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code this
    is ASM when the C/C++11 should already do it for me. That type of
    angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in gcc's
    C++ standard library, rather than a direct lock-free opcode.˙ I don't
    know the details of any of this - maybe there are flaws in some
    chips, maybe the single op-code actually takes longer than a lock-
    based implementation.˙ Maybe it's just that no one has got round to
    implementing it yet.

    I would guess the reason is consistent behaviour across different
    architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need it
    for then that would be okay. We need to step into the library call to
    find out.

    From what I have looked at, including the bugzilla link Joseph posted,
    there seem to be a number of complications involved. A particular
    sticking point is that you can't have lock-free C++ standard atomic
    types without also having atomic load and store operations of the same
    size. And while the 16-byte CAS can be used for loads and stores, it
    can't be used for 16-byte atomic loads on read-only memory. This means
    that on older 64-bit x86 processors, 16-byte atomic types cannot be
    lock-free even if they support 16-byte CAS.

    It seems that the libatomic implementation does, in some cases, use the CMPXCHG16B instruction rather than a mutex. The exact code run in the
    library depends on the cpuid of the processor, as well as compiler
    flags. (The default "lowest common denominator" x86-64 instruction set
    does not support CMPXCHG16B.)

    So the C++ standard library atomics do not lead directly to the use of
    the lock-free instruction. But if you have the "-mxc16" compiler flag
    (or use a -march flag for a cpu family that supports it), then you can
    use the low-level builtin __sync_bool_compare_and_swap to generate it.

    Better still, in gcc 17 (in development) you can use the
    "-m128bit-atomic" flag to get full 16-byte atomics on processors for
    which the GCC folks have assurances from Intel or AMD that the 128-bit
    SSE2 vector loads and stores are atomic. You can try this on godbolt.org.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Mon Oct 5 19:00:24 2026
    Am 17.09.2026 um 00:25 schrieb Joseph Seigh:

    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/
    RCU isn't necessary. If shared_ptr<X> would have an assignment operator
    that takes an atomic<shared_ptr<X>> on the right side and this assign-
    ment operator would first compare its own pointer vs the current poin-
    ter of the atomic. If both are the same nothing further happens - and
    the cachelines holding the atomic<shared_ptr<X>> could remain in shared
    mode.
    I implemented that and the following code is the assignment-operator
    of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first
    method and the slow path is in the second method:

    template<typename T, int ThreadMode>
    FORCEINLINE auto shared_obj<T, ThreadMode>::operator =( const
    tshared_obj<T, ThreadMode> &tso ) noexcept -> shared_obj &
    {
    data *pDtaRef = tso.m_pAtmData.load( std::memory_order_relaxed );
    if( m_pData != pDtaRef ) [[unlikely]]
    slowAssign( tso, pDtaRef );
    return *this;
    }

    template<typename T, int ThreadMode>
    NOINLINE void shared_obj<T, ThreadMode>::slowAssign( const
    tshared_obj<T, ThreadMode> &tso, data *pDtaRef ) noexcept
    {
    using namespace std;
    for( ; ; )
    {
    if( !pDtaRef )
    {
    data *pData = m_pData;
    if( pData && pData->decr() )
    delete pData;
    m_pData = nullptr;
    return;
    }
    if( pDtaRef == (data *)-1 )
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef, (data *)-1,
    memory_order_relaxed, memory_order_relaxed ) ) [[likely]]
    break;
    if( m_pData == pDtaRef )
    return;
    }
    m_pData = pDtaRef;
    m_pData->incr( memory_order_acquire );
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    tso.m_pAtmData.notify_all();
    }

    I ran the numbers on that and an assignment takes about a nanosecond
    if the tshared_ptr hasn't changed. I ran the same benchmark on a
    shared _ptr<X> vs a atomic<shared_ptr<X>> and depending on the num-
    ber of threads the time per operation goes up to several microseconds.

    And my thshared_ptr only has the size of a pointer because when it
    is locked it morphes into a futex having the locked value of -1.
    The usual atomic<shared_ptr<X>>s have the size of two pointers.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Oct 5 10:57:10 2026
    On 10/5/2026 10:00 AM, Bonita Montero wrote:
    Am 17.09.2026 um 00:25 schrieb Joseph Seigh:

    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/
    RCU isn't necessary. If shared_ptr<X> would have an assignment operator
    that takes an atomic<shared_ptr<X>> on the right side and this assign-
    ment operator would first compare its own pointer vs the current poin-
    ter of the atomic. If both are the same nothing further happens - and
    the cachelines holding the atomic<shared_ptr<X>> could remain in shared
    mode.
    I implemented that and the following code is the assignment-operator
    of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first method and the slow path is in the second method:[...]

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Mon Oct 5 21:03:43 2026
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:
    On 10/5/2026 10:00 AM, Bonita Montero wrote:
    Am 17.09.2026 um 00:25 schrieb Joseph Seigh:

    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/
    RCU isn't necessary. If shared_ptr<X> would have an assignment operator
    that takes an atomic<shared_ptr<X>> on the right side and this assign-
    ment operator would first compare its own pointer vs the current poin-
    ter of the atomic. If both are the same nothing further happens - and
    the cachelines holding the atomic<shared_ptr<X>> could remain in shared
    mode.
    I implemented that and the following code is the assignment-operator
    of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first
    method and the slow path is in the second method:[...]

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    If I try to update my shared_obj<T> from a tshared_obj<T> and both are
    the same there are no atomic RMW-operations. Just a read of the pointer
    in memory which is in shared state in the cache. The update is just
    about a nanosecond if the tshared_obj<T> is still the same as at the
    last time I updated my shared_obj<T>.
    RCU is rather silly compared to that.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Mon Oct 5 21:23:16 2026
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data.
    Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't
    change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times
    slower:

    https://ibb.co/nqGHv9pL

    This is the complete source:

    #pragma once
    #include <atomic>
    #include <concepts>
    #include <type_traits>
    #include <cassert>
    #include "cacheline.hpp"
    #include "inline.hpp"

    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdangling-else"
    #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
    #endif

    template<typename T, int ThreadMode>
    struct shared_obj;

    template<typename T, int ThreadMode, typename ... Args>
    shared_obj<T, ThreadMode> make_shared_obj( Args &&... args );
    template<typename T, int ThreadMode>
    struct tshared_obj;

    template<typename T, int ThreadMode>
    struct shared_obj
    {
    shared_obj() noexcept = default;
    shared_obj( const tshared_obj<T, ThreadMode> & ) noexcept;
    shared_obj( tshared_obj<T, ThreadMode> && ) noexcept;
    template<typename T2>
    shared_obj( const shared_obj<T2, ThreadMode> &so ) noexcept;
    template<typename T2>
    shared_obj( shared_obj<T2, ThreadMode> &&so ) noexcept;
    shared_obj &operator =( const shared_obj & ) noexcept;
    shared_obj &operator =( shared_obj && ) noexcept;
    shared_obj &operator =( const tshared_obj<T, ThreadMode> & ) noexcept;
    shared_obj &operator =( tshared_obj<T, ThreadMode> && ) noexcept;
    ~shared_obj();
    T *get() const noexcept;
    operator bool() const noexcept;
    uint32_t use_count() const noexcept;
    T &operator *() const noexcept;
    T *operator ->() const noexcept;
    void reset() noexcept;
    private:
    static_assert( std::is_nothrow_destructible_v<T> );
    template<typename T2, int ThreadMode2, typename ... Args>
    friend shared_obj<T2, ThreadMode2> make_shared_obj( Args &&... args );
    template<typename T2, int ThreadMode2>
    friend struct tshared_obj;
    template<typename T2, int ThreadMode2>
    friend struct shared_obj;
    static constexpr size_t
    RcAlign = ThreadMode > 0 ? CL_SIZE : alignof( uint32_t ),
    TotalAlign = std::max( ThreadMode < 0 ? CL_SIZE : alignof( T ), RcAlign );
    struct alignas( RcAlign ) ref_count
    {
    uint32_t m_refCount;
    template<std::memory_order Order = std::memory_order_relaxed>
    void incr() noexcept;
    bool decr() noexcept;
    };
    struct non_class_value_type
    {
    T m_value;
    };
    static constexpr bool ValueIsClass = std::is_class_v<T>;
    using value_type = std::conditional_t<ValueIsClass, T, non_class_value_type>;
    struct alignas( TotalAlign ) data : ref_count, value_type
    {
    template<typename ... Args>
    data( Args &&... args );
    };
    shared_obj( data *obj ) noexcept;
    template<typename T2>
    data *assign( const shared_obj<T2, ThreadMode> & ) noexcept;
    data *m_pData = nullptr;
    void slowAssign( const tshared_obj<T, ThreadMode> &tso, data *pDtaRef )
    noexcept;
    };

    template<typename T, int ThreadMode>
    template<std::memory_order Order>
    FORCEINLINE void shared_obj<T, ThreadMode>::ref_count::incr() noexcept
    {
    uint32_t before;
    if constexpr( ThreadMode )
    before = std::atomic_ref( m_refCount ).fetch_add( 1, Order );
    else
    before = m_refCount++;
    assert(before && before != (unsigned)-1);
    return (void)before;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE bool shared_obj<T, ThreadMode>::ref_count::decr() noexcept
    {
    if constexpr( ThreadMode )
    return std::atomic_ref( m_refCount ).fetch_sub( 1, std::memory_order_relaxed ) == 1;
    else
    return !--m_refCount;
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    inline shared_obj<T, ThreadMode>::shared_obj( const shared_obj<T2,
    ThreadMode> &so ) noexcept :
    m_pData( assign( so ) )
    {
    if( m_pData )
    m_pData->incr();
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    inline shared_obj<T, ThreadMode>::shared_obj( shared_obj<T2, ThreadMode>
    &&so ) noexcept :
    m_pData( assign( so ) )
    {
    so.m_pData = nullptr;
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    FORCEINLINE auto shared_obj<T, ThreadMode>::assign( const shared_obj<T2, ThreadMode> &so ) noexcept -> data *
    {
    using namespace std;
    constexpr bool Polymorphic = !same_as<T, T2> && derived_from<T, T2> && is_polymorphic_v<T2>;
    static_assert( same_as<T, T2> || derived_from<T2, T> || Polymorphic );
    if( !so.m_pData )
    return nullptr;
    if constexpr( Polymorphic )
    if( !dynamic_cast<T *>( &so.m_pData->m_value ) )
    return nullptr;
    return (data *)so.m_pData;
    }

    template<typename T, int ThreadMode>
    template<typename ... Args>
    inline shared_obj<T, ThreadMode>::data::data( Args &&... args ) :
    ref_count( 1 ),
    value_type( T( std::forward<Args>( args ) ... ) )
    {
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =(
    shared_obj &&other ) noexcept
    {
    if( m_pData == other.m_pData ) [[unlikely]]
    {
    other.m_pData = nullptr;
    if( m_pData ) [[likely]]
    m_pData->decr();
    return *this;
    }
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = other.m_pData;
    other.m_pData = nullptr;
    return *this;
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =( const shared_obj &other ) noexcept
    {
    if( m_pData == other.m_pData )
    return *this;
    if( m_pData && m_pData->decr() )
    {
    delete m_pData;
    m_pData = nullptr;
    }
    if( !other.m_pData )
    return *this;
    m_pData = other.m_pData;
    m_pData->incr();
    return *this;
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode>::~shared_obj()
    {
    if( m_pData && m_pData->decr() )
    delete m_pData;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T *shared_obj<T, ThreadMode>::get() const noexcept
    {
    if constexpr( ValueIsClass )
    return static_cast<T *>( m_pData );
    else
    return &m_pData->m_value;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::operator bool() const noexcept
    {
    return (bool)m_pData;
    }

    template<typename T, int ThreadMode>
    uint32_t shared_obj<T, ThreadMode>::use_count() const noexcept
    {
    if( !m_pData )
    return 0;
    if constexpr( ThreadMode )
    return std::atomic_ref( m_pData->m_refCount ).load( std::memory_order_relaxed );
    else
    return m_pData->m_refCount;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( data *pData ) noexcept :
    m_pData( pData )
    {
    assert(!m_pData || m_pData->m_refCount);
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T &shared_obj<T, ThreadMode>::operator *() const noexcept
    {
    assert(m_pData);
    return *get();
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T *shared_obj<T, ThreadMode>::operator ->() const noexcept
    {
    assert(m_pData);
    return get();
    }

    template<typename T, int ThreadMode>
    void shared_obj<T, ThreadMode>::reset() noexcept
    {
    if( !m_pData )
    return;
    if( m_pData->decr() )
    delete m_pData;
    m_pData = nullptr;
    }
    template<typename T, int ThreadMode, typename ... Args>
    shared_obj<T, ThreadMode> make_shared_obj( Args &&... args )
    {
    using data = shared_obj<T, ThreadMode>::data;
    return shared_obj<T, ThreadMode>( new data( std::forward<Args>( args ) ... ) );
    }

    template<typename T, int ThreadMode>
    struct tshared_obj
    {
    tshared_obj() noexcept = default;
    tshared_obj( const shared_obj<T, ThreadMode> & ) noexcept;
    tshared_obj( shared_obj<T, ThreadMode> && ) noexcept;
    ~tshared_obj();
    tshared_obj &operator =( const shared_obj<T, ThreadMode> & ) noexcept;
    tshared_obj &operator =( shared_obj<T, ThreadMode> && ) noexcept;
    void exchange( shared_obj<T, ThreadMode> &so );
    void reset() noexcept;
    private:
    static_assert( ThreadMode );
    using data = shared_obj<T, ThreadMode>::data;
    template<typename T2, int ThreadMode2>
    friend struct shared_obj;
    mutable std::atomic<data *> m_pAtmData = nullptr;
    };

    template<typename T, int ThreadMode>
    FORCEINLINE auto shared_obj<T, ThreadMode>::operator =( const
    tshared_obj<T, ThreadMode> &tso ) noexcept -> shared_obj &
    {
    data *pDtaRef = tso.m_pAtmData.load( std::memory_order_relaxed );
    if( m_pData != pDtaRef ) [[unlikely]]
    slowAssign( tso, pDtaRef );
    return *this;
    }

    template<typename T, int ThreadMode>
    NOINLINE void shared_obj<T, ThreadMode>::slowAssign( const
    tshared_obj<T, ThreadMode> &tso, data *pDtaRef ) noexcept
    {
    using namespace std;
    for( ; ; )
    {
    if( !pDtaRef )
    {
    data *pData = m_pData;
    if( pData && pData->decr() )
    delete pData;
    m_pData = nullptr;
    return;
    }
    if( pDtaRef == (data *)-1 )
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef, (data *)-1,
    memory_order_relaxed, memory_order_relaxed ) ) [[likely]]
    break;
    if( m_pData == pDtaRef )
    return;
    }
    m_pData = pDtaRef;
    m_pData->incr<memory_order_acquire>();
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    tso.m_pAtmData.notify_all();
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =( tshared_obj<T, ThreadMode> &&tso ) noexcept
    {
    using namespace std;
    data *pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( !pDtaRef )
    {
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = nullptr;
    return *this;
    }
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef, nullptr,
    memory_order_acquire, memory_order_relaxed ) )
    break;
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = pDtaRef;
    return *this;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( const tshared_obj<T, ThreadMode> &tso ) noexcept :
    m_pData( nullptr )
    {
    *this = tso;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( tshared_obj<T,
    ThreadMode> &&tso ) noexcept :
    m_pData( nullptr )
    {
    *this = std::move( tso );
    }

    template<typename T, int ThreadMode>
    tshared_obj<T, ThreadMode>::tshared_obj( const shared_obj<T, ThreadMode>
    &so ) noexcept :
    m_pAtmData( so.m_pData )
    {
    if( so.m_pData )
    so.m_pData->incr( std::memory_order_release );
    }

    template<typename T, int ThreadMode>
    inline tshared_obj<T, ThreadMode>::tshared_obj( shared_obj<T,
    ThreadMode> &&so ) noexcept :
    m_pAtmData( so.m_pData )
    {
    so.m_pData = nullptr;
    }

    template<typename T, int ThreadMode>
    tshared_obj<T, ThreadMode>::~tshared_obj()
    {
    data *pAtmData = m_pAtmData.load( std::memory_order_relaxed );
    if( pAtmData && pAtmData->decr() )
    delete pAtmData;
    }

    template<typename T, int ThreadMode>
    NOINLINE auto tshared_obj<T, ThreadMode>::operator =( const
    shared_obj<T, ThreadMode> &so ) noexcept -> tshared_obj &
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData ) [[likely]]
    return *this;
    else if( pDtaRef == (data *)-1 )
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, (data *)-1,
    memory_order_relaxed, memory_order_relaxed ) )
    break;
    if( pDtaRef && pDtaRef->decr() )
    delete pDtaRef;
    so.m_pData->incr();
    m_pAtmData.store( so.m_pData, memory_order_release );
    m_pAtmData.notify_all();
    return *this;
    }

    template<typename T, int ThreadMode>
    auto tshared_obj<T, ThreadMode>::operator =( shared_obj<T, ThreadMode>
    &&so ) noexcept -> tshared_obj &
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData )
    {
    if( !so.m_pData )
    return *this;
    so.m_pData->decr();
    so.m_pData = nullptr;
    return *this;
    }
    else if( m_pAtmData == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, so.m_pData,
    memory_order_release, memory_order_relaxed ) )
    break;
    if( pDtaRef && pDtaRef->decr() )
    delete pDtaRef;
    so.m_pData = nullptr;
    return *this;
    }

    template<typename T, int ThreadMode>
    void tshared_obj<T, ThreadMode>::exchange( shared_obj<T, ThreadMode> &so )
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData )
    return;
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, so.m_pData,
    memory_order_acq_rel, memory_order_relaxed ) )
    break;
    so.m_pData = pDtaRef;
    }

    template<typename T, int ThreadMode>
    void tshared_obj<T, ThreadMode>::reset() noexcept
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( !pDtaRef )
    return;
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, nullptr, memory_order_relaxed, memory_order_relaxed ) )
    break;
    if( pDtaRef->decr() )
    delete pDtaRef;
    }

    #if defined(__clang__)
    #pragma clang diagnostic pop
    #endif

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Oct 6 06:28:38 2026
    Am 29.09.2026 um 08:40 schrieb David Brown:

    The whole point of using lock-free algorithms here, and the double compare-and-swap, is speed.˙ If you use external assembly so that all
    your important data has to be pushed from registers to the stack, then
    you have a call, then you pull the data off the stack again - you've a
    dozen extra instructions at the call site, a dozen extra instructions at
    the cas16 site, doing nothing except spoiling your efficient code.

    A CAS16 is that slow that the additional call doesn't hurt.
    And usually a CAS involves remote cacheline re-loading which
    is a magnitude more expensive than a trivial function call.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Oct 6 08:36:52 2026
    On 06/10/2026 06:28, Bonita Montero wrote:
    Am 29.09.2026 um 08:40 schrieb David Brown:

    The whole point of using lock-free algorithms here, and the double
    compare-and-swap, is speed.˙ If you use external assembly so that all
    your important data has to be pushed from registers to the stack, then
    you have a call, then you pull the data off the stack again - you've a
    dozen extra instructions at the call site, a dozen extra instructions
    at the cas16 site, doing nothing except spoiling your efficient code.

    A CAS16 is that slow that the additional call doesn't hurt.
    And usually a CAS involves remote cacheline re-loading which
    is a magnitude more expensive than a trivial function call.


    I am not an expert in these algorithms and their timings, but I would
    have thought this only applies when there has been a conflict - some
    other thread/cpu has been accessing the data structure since you last
    used it.

    And equally, while a function call can have low overheads if everything
    is in the instruction cache, if the code for it has to be pulled in from
    main memory then it will add significant extra time.

    Still, it is a relevant point that real-life timing is more than just
    the number of instructions.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Oct 6 10:38:27 2026
    Am 06.10.2026 um 08:36 schrieb David Brown:

    And equally, while a function call can have low overheads if everything
    is in the instruction cache, if the code for it has to be pulled in from main memory then it will add significant extra time.

    Absolutely not, theres an instruction and a uOp cache.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Oct 6 10:42:38 2026
    On 06/10/2026 10:38, Bonita Montero wrote:
    Am 06.10.2026 um 08:36 schrieb David Brown:

    And equally, while a function call can have low overheads if
    everything is in the instruction cache, if the code for it has to be
    pulled in from main memory then it will add significant extra time.

    Absolutely not, theres an instruction and a uOp cache.


    Please re-read what I wrote.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 13:15:57 2026
    On 10/5/2026 12:03 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:
    On 10/5/2026 10:00 AM, Bonita Montero wrote:
    Am 17.09.2026 um 00:25 schrieb Joseph Seigh:

    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/
    RCU isn't necessary. If shared_ptr<X> would have an assignment operator
    that takes an atomic<shared_ptr<X>> on the right side and this assign-
    ment operator would first compare its own pointer vs the current poin-
    ter of the atomic. If both are the same nothing further happens - and
    the cachelines holding the atomic<shared_ptr<X>> could remain in shared
    mode.
    I implemented that and the following code is the assignment-operator
    of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first
    method and the slow path is in the second method:[...]

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    If I try to update my shared_obj<T> from a tshared_obj<T> and both are
    the same there are no atomic RMW-operations. Just a read of the pointer
    in memory which is in shared state in the cache. The update is just
    about a nanosecond if the tshared_obj<T> is still the same as at the
    last time I updated my shared_obj<T>.
    RCU is rather silly compared to that.


    It seems like you are trying to compare using RCU to actually implement
    a strong atomic ref counted pointer. Of course RCU s not needed for
    that. Afaict, you might be missing the forest for the trees?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 13:18:26 2026
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times slower:[...]

    Afaict, you are into the internals of implementing a strong atomic
    refcount. RCU can be used for that, but why do it? RCU is not really
    meant for that. Think of a RCU read side algo. It does not need any
    atomic RMW or any membars, like I said, alpha aside for a moment.

    Joe already has basically the best strong atomic ref count out there. No
    RCU involved.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 13:19:38 2026
    On 10/6/2026 1:42 AM, David Brown wrote:
    On 06/10/2026 10:38, Bonita Montero wrote:
    Am 06.10.2026 um 08:36 schrieb David Brown:

    And equally, while a function call can have low overheads if
    everything is in the instruction cache, if the code for it has to be
    pulled in from main memory then it will add significant extra time.

    Absolutely not, theres an instruction and a uOp cache.


    Please re-read what I wrote.


    I "think" he is confusing things... Using RCU for hyper fast read side,
    vs implementing the guts of a strong atomic ref counter.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 7 05:19:22 2026
    Am 06.10.2026 um 22:15 schrieb Chris M. Thomasson:

    It seems like you are trying to compare using RCU to actually implement
    a strong atomic ref counted pointer. Of course RCU s not needed for
    that. Afaict, you might be missing the forest for the trees?

    You don't understand my idea.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 7 05:19:59 2026
    Am 06.10.2026 um 22:18 schrieb Chris M. Thomasson:

    Afaict, you are into the internals of implementing a strong atomic
    refcount. ...

    You don't understand my idea - check the code.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 21:29:36 2026
    On 10/6/2026 8:19 PM, Bonita Montero wrote:
    Am 06.10.2026 um 22:15 schrieb Chris M. Thomasson:

    It seems like you are trying to compare using RCU to actually
    implement a strong atomic ref counted pointer. Of course RCU s not
    needed for that. Afaict, you might be missing the forest for the trees?

    You don't understand my idea.


    Are you trying to make a strong atomic ref count safe or not? RCU really
    does not have to have anything to do with that.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 21:31:35 2026
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times
    slower:

    ˙˙˙˙https://ibb.co/nqGHv9pL

    This is the complete source:
    [...]

    Fwiw, I posted my older proxy collector that can gain RCU like
    performance wrt amortized use in user space. It does not use CAS, just fetch_add.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 21:33:57 2026
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times
    slower:
    Bench mark it against Joes atomic ptr that uses DWCAS. Keep in mind that
    we can use it to make a proxy collector that has RCU like performance as
    well. Keep in mind that it involves a thread being able to take a
    reference to something it does no already own. No locks. Once a
    local_ptr is gained its basically equal to basic thread saftey normal
    atomic refcount.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 6 21:34:49 2026
    On 10/6/2026 9:33 PM, Chris M. Thomasson wrote:
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data.
    Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an
    atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't
    change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times
    slower:
    Bench mark it against Joes atomic ptr that uses DWCAS. Keep in mind that
    we can use it to make a proxy collector that has RCU like performance as well. Keep in mind that it involves a thread being able to take a
    reference to something it does no already own. No locks. Once a
    local_ptr is gained its basically equal to basic thread saftey normal
    atomic refcount.

    No locks, no futex, etc...

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Oct 7 10:49:49 2026
    On 06/10/2026 22:19, Chris M. Thomasson wrote:
    On 10/6/2026 1:42 AM, David Brown wrote:
    On 06/10/2026 10:38, Bonita Montero wrote:
    Am 06.10.2026 um 08:36 schrieb David Brown:

    And equally, while a function call can have low overheads if
    everything is in the instruction cache, if the code for it has to be
    pulled in from main memory then it will add significant extra time.

    Absolutely not, theres an instruction and a uOp cache.


    Please re-read what I wrote.


    I "think" he is confusing things... Using RCU for hyper fast read side,
    vs implementing the guts of a strong atomic ref counter.

    I am not sure you read the posts either.

    I wrote "while a function call can have low overheads if everything is
    in the instruction cache ...", and Bonita replied "Absolutely not,
    there's an instruction cache".

    Then you followed up with something completely different.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 7 15:00:26 2026
    Am 07.10.2026 um 06:33 schrieb Chris M. Thomasson:

    Bench mark it against Joes atomic ptr that uses DWCAS. ...

    You still dont' understand my idea. Imagine you've got a shared_ptr<X>
    and you constantly poll an atomic<shared_ptr<X>> with this to ger a
    up to date version of the shared data. With the current atomic impl.
    the at<sp<X>> is converted to a sp<X> and then this value is move
    -assigned to the sp<X>. This has a big overhad through the atomic
    increments of the reference counter.
    Imagine now that the at<sp<X>> has an assignment operator that takes
    an sp<X> reference directly and not only for it's own type. This ope-
    rator could simply compare its own value with the at<sp<X>>'s value
    and if this value hasn't changed it does nothing further. This would
    mean as long as there are no updates of the at<sp<X>> this operation
    would proceed in a nanosecond since the shared data and it's reference counter's cache lines would remain in shared mode and there would be
    no additional cachline loads.
    With RCU you take a snapshot of some shared data, read it, mark it
    as discadable and the cleanup would take place asynchronously. With
    my idea that's not much different since polling and updating the
    thread-local "sp<X>" nearly costs nothing.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 7 15:01:44 2026
    Am 07.10.2026 um 06:29 schrieb Chris M. Thomasson:

    Are you trying to make a strong atomic ref count safe or not?
    RCU really does not have to have anything to do with that.

    My idea is totally different from RCU, but you can do the same
    thing with it with nearly no overhad.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 7 15:26:12 2026
    This is an example where I used my shared_obj<T>, which is constantly poll-updated from a tshared_obj<T>. As long as the latter doesn't change
    this is nearly for free.

    template<bool Utc, bool AlsoEqual>
    auto date_1601::lookUpLeap( int64_t ts ) noexcept -> lulp_ret
    {
    thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;
    pLeaps = g_leapSeconds; // update
    span leaps = pLeaps->m_leaps;
    auto
    itBegin = leaps.rbegin(),
    itEnd = leaps.rend(),
    itBefore = itBegin;
    if constexpr( AlsoEqual )
    itBefore = lower_bound( itBegin, itEnd, ts, []( const dual_deaps
    &leap, int64_t ref ) { return leap[Utc] > ref; } );
    else
    itBefore = upper_bound( itBegin, itEnd, ts, []( int64_t ref, const
    dual_deaps &leap ) { return ref > leap[Utc]; } );
    if( itBefore == itEnd )
    return lulp_ret( -1, 0 );
    return lulp_ret( leaps.rend() - itBefore - 1, (*itBefore)[Utc] );
    }

    In this example I'm dealing with a central storage of leap seconds which
    may been updated asynchronously. This updates woudln't happen so often,
    meaning the commented update assingment is nearly for free.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 7 13:11:25 2026
    On 10/7/2026 1:49 AM, David Brown wrote:
    On 06/10/2026 22:19, Chris M. Thomasson wrote:
    On 10/6/2026 1:42 AM, David Brown wrote:
    On 06/10/2026 10:38, Bonita Montero wrote:
    Am 06.10.2026 um 08:36 schrieb David Brown:

    And equally, while a function call can have low overheads if
    everything is in the instruction cache, if the code for it has to
    be pulled in from main memory then it will add significant extra time. >>>>
    Absolutely not, theres an instruction and a uOp cache.


    Please re-read what I wrote.


    I "think" he is confusing things... Using RCU for hyper fast read
    side, vs implementing the guts of a strong atomic ref counter.

    I am not sure you read the posts either.

    I wrote "while a function call can have low overheads if everything is
    in the instruction cache ...", and Bonita replied "Absolutely not,
    there's an instruction cache".

    Then you followed up with something completely different.



    oh well. I did! I was responding to the guts of Bonita's ref count impl. Sorry!

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 7 13:12:04 2026
    On 10/7/2026 6:01 AM, Bonita Montero wrote:
    Am 07.10.2026 um 06:29 schrieb Chris M. Thomasson:

    Are you trying to make a strong atomic ref count safe or not?
    RCU really does not have to have anything to do with that.

    My idea is totally different from RCU, but you can do the same
    thing with it with nearly no overhad.


    So, you are making a proxy collector? I am quite fond of them. Joe knows
    all about them.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 7 13:13:43 2026
    On 10/7/2026 6:00 AM, Bonita Montero wrote:
    Am 07.10.2026 um 06:33 schrieb Chris M. Thomasson:

    Bench mark it against Joes atomic ptr that uses DWCAS. ...

    You still dont' understand my idea. Imagine you've got a shared_ptr<X>
    and you constantly poll an atomic<shared_ptr<X>> with this to ger a
    up to date version of the shared data. With the current atomic impl.
    the at<sp<X>> is converted to a sp<X> and then this value is move
    -assigned to the sp<X>. This has a big overhad through the atomic
    increments of the reference counter.
    Imagine now that the at<sp<X>> has an assignment operator that takes
    an sp<X> reference directly and not only for it's own type. This ope-
    rator could simply compare its own value with the at<sp<X>>'s value
    and if this value hasn't changed it does nothing further. This would
    mean as long as there are no updates of the at<sp<X>> this operation
    would proceed in a nanosecond since the shared data and it's reference counter's cache lines would remain in shared mode and there would be
    no additional cachline loads.
    With RCU you take a snapshot of some shared data, read it, mark it
    as discadable and the cleanup would take place asynchronously.

    Huh? A snapshot? What are you talking about? I don't think you know what
    a read side iteration of say, a single linked list looks like.



    With
    my idea that's not much different since polling and updating the
    thread-local "sp<X>" nearly costs nothing.


    Huh? You are either making a strong atomic refcount or not.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 7 13:17:29 2026
    On 10/7/2026 6:26 AM, Bonita Montero wrote:
    This is an example where I used my shared_obj<T>, which is constantly poll-updated from a tshared_obj<T>. As long as the latter doesn't change
    this is nearly for free.

    template<bool Utc, bool AlsoEqual>
    auto date_1601::lookUpLeap( int64_t ts ) noexcept -> lulp_ret
    {
    ˙˙˙˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;
    ˙˙˙˙pLeaps = g_leapSeconds; // update
    ˙˙˙˙span leaps = pLeaps->m_leaps;
    ˙˙˙˙auto
    ˙˙˙˙˙˙˙ itBegin = leaps.rbegin(),
    ˙˙˙˙˙˙˙ itEnd = leaps.rend(),
    ˙˙˙˙˙˙˙ itBefore = itBegin;
    ˙˙˙˙if constexpr( AlsoEqual )
    ˙˙˙˙˙˙˙ itBefore = lower_bound( itBegin, itEnd, ts, []( const
    dual_deaps &leap, int64_t ref ) { return leap[Utc] > ref; } );
    ˙˙˙˙else
    ˙˙˙˙˙˙˙ itBefore = upper_bound( itBegin, itEnd, ts, []( int64_t ref,
    const dual_deaps &leap ) { return ref > leap[Utc]; } );
    ˙˙˙˙if( itBefore == itEnd )
    ˙˙˙˙˙˙˙ return lulp_ret( -1, 0 );
    ˙˙˙˙return lulp_ret( leaps.rend() - itBefore - 1, (*itBefore)[Utc] );
    }

    In this example I'm dealing with a central storage of leap seconds which
    may been updated asynchronously. This updates woudln't happen so often, meaning the commented update assingment is nearly for free.



    Can you iterate a shared single linked list with basically zero overhead?

    thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;

    ? So, are you taking a reference to something the thread does not own? I noticed you mentioned futex. But that implies waiting! Or else why use
    it to begin with?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Oct 8 05:55:20 2026
    Am 07.10.2026 um 22:17 schrieb Chris M. Thomasson:

    ˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;

    ? So, are you taking a reference to something the thread does not own?
    Like with RCU no thread owns the data once its published since it is
    read only.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Oct 8 05:56:32 2026
    Am 07.10.2026 um 22:13 schrieb Chris M. Thomasson:

    Huh? You are either making a strong atomic refcount or not.

    With RCU a newer version of the dater may have been published and a
    thread is working with an older version. With my idea it's the same.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Oct 8 05:57:19 2026
    Am 07.10.2026 um 22:12 schrieb Chris M. Thomasson:

    So, you are making a proxy collector? I am quite fond of them. Joe knows
    all about them.

    I think Joe understood my idea and you didn't.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 11:39:07 2026
    On 10/2/2026 11:39 AM, Joseph Seigh wrote:
    On 9/29/26 7:30 PM, Chris M. Thomasson wrote:
    On 9/28/2026 11:40 PM, David Brown wrote:
    On 29/09/2026 00:42, Chris M. Thomasson wrote:
    ...

    (Of course for this particular case, if you can persuade the gcc C++
    library folk to make their standard cas16 lock-free, then using the
    standard function is best.)
    Difficult to totally disagree with that David. Shit. Its my own
    personal problem with the syntax of GCC inline asm. I am a bit scared
    of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
    externally assembled functions are fine for me, LTO aside for a
    moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
    personal problem wrt me saying inline bad! Sorry!

    Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
    64 bit words on a 64 bit machine, well GOOD! Show me. I have had some
    experiences with it trying to tell me its not lock free.

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878

    The rationale seems to be you can mix a lock-free operation
    with locked operations since __atomic_always_lock_free and __atomic_is_lock_free apply to the data type not specific
    builtins.

    Given that there seems to be no shortage of x86 specific
    gcc builtins, I can't think of any reason why there's
    no cmpxchg16b builtin.

    Really strange!

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 11:41:49 2026
    On 10/7/2026 8:57 PM, Bonita Montero wrote:
    Am 07.10.2026 um 22:12 schrieb Chris M. Thomasson:

    So, you are making a proxy collector? I am quite fond of them. Joe
    knows all about them.

    I think Joe understood my idea and you didn't.


    I think you should use his atomic_ptr and be done with it. It pure
    lock-free. Yours is not:
    ____________
    NOINLINE void shared_obj<T, ThreadMode>::slowAssign( const
    tshared_obj<T, ThreadMode> &tso, data *pDtaRef ) noexcept
    {
    using namespace std;
    for( ; ; )
    {
    if( !pDtaRef )
    {
    data *pData = m_pData;
    if( pData && pData->decr() )
    delete pData;
    m_pData = nullptr;
    return;
    }
    if( pDtaRef == (data *)-1 )
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef, (data
    *)-1, memory_order_relaxed, memory_order_relaxed ) ) [[likely]]
    break;
    if( m_pData == pDtaRef )
    return;
    }
    m_pData = pDtaRef;
    m_pData->incr( memory_order_acquire );
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    tso.m_pAtmData.notify_all();
    }
    ____________

    Sigh.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 12:19:44 2026
    On 10/7/2026 8:55 PM, Bonita Montero wrote:
    Am 07.10.2026 um 22:17 schrieb Chris M. Thomasson:

    ˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;

    ? So, are you taking a reference to something the thread does not own?
    Like with RCU no thread owns the data once its published since it is
    read only.

    Well, your work sure seems to act like a proxy. But it uses CAS and
    futex. Therefore its out of scope with anything lock-free.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 14:20:57 2026
    On 10/8/2026 12:19 PM, Chris M. Thomasson wrote:
    On 10/7/2026 8:55 PM, Bonita Montero wrote:
    Am 07.10.2026 um 22:17 schrieb Chris M. Thomasson:

    ˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;

    ? So, are you taking a reference to something the thread does not own?
    Like with RCU no thread owns the data once its published since it is
    read only.

    Well, your work sure seems to act like a proxy. But it uses CAS and
    futex. Therefore its out of scope with anything lock-free.

    For instance. Take my most recent post "CAS free Proxy Collector..."

    https://pastebin.com/raw/CYZ78gVj

    Notice how it can allow lock-free iterations of a lock-free stack that
    does not use any DWCAS, no ABA? How can we use yours for the same thing?
    No lock, no futex, 100% lock-free etc? Heck on systems that don't use
    LL/SC loops for fetch and add, well its wait-free on the fast path. For instance:

    // you're basic lock-free stack...
    // well, minus ABA counter and DWCAS of course! ;^)
    class ct_stack
    {
    std::atomic<ct_node*> m_head;


    public:
    ct_stack() : m_head(nullptr)
    {

    }


    public:
    void push(ct_node* n)
    {
    ct_node* head = m_head.load(std::memory_order_relaxed);

    do
    {
    n->m_next.store(head, std::memory_order_relaxed);
    }

    while (! m_head.compare_exchange_weak(
    head,
    n,
    std::memory_order_release));
    }


    ct_node* flush()
    {
    return m_head.exchange(nullptr, std::memory_order_acquire);
    }


    ct_node* get_head()
    {
    return m_head.load(std::memory_order_acquire);
    }


    ct_node* pop()
    {
    ct_node* head = m_head.load(std::memory_order_acquire);
    ct_node* xchg;

    do
    {
    if (! head) return nullptr;

    xchg = head->m_next.load(std::memory_order_relaxed);
    }

    while (!m_head.compare_exchange_weak(
    head,
    xchg,
    std::memory_order_acquire));

    return head;
    }
    };



    With iterations with heavy read and some write activity.

    // Reader threads
    // Iterates through the lock free stack
    void ct_thread_reader(ct_shared& shared)
    {
    // iterate the lockfree stack
    for (unsigned long i = 0; i < ct_reader_iters_n; ++i)
    {
    ct_proxy_collector::collector& c = shared.m_proxy_gc.acquire();

    ct_node* n = shared.m_stack.get_head();

    while (n)
    {
    // need to add in some processing...
    // std::this_thread::yield();

    n = n->m_next.load(std::memory_order_relaxed);
    }

    shared.m_proxy_gc.release(c);
    }
    }

    Ideally the shared.m_stack should have a lot of nodes to make it worth while...

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 17:13:10 2026
    On 10/4/2026 2:34 AM, David Brown wrote:
    On 03/10/2026 23:12, Chris M. Thomasson wrote:
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code this >>>>> is ASM when the C/C++11 should already do it for me. That type of
    angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in gcc's
    C++ standard library, rather than a direct lock-free opcode.˙ I
    don't know the details of any of this - maybe there are flaws in
    some chips, maybe the single op-code actually takes longer than a
    lock- based implementation.˙ Maybe it's just that no one has got
    round to implementing it yet.

    I would guess the reason is consistent behaviour across different
    architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need it
    for then that would be okay. We need to step into the library call to
    find out.

    From what I have looked at, including the bugzilla link Joseph posted, there seem to be a number of complications involved.˙ A particular
    sticking point is that you can't have lock-free C++ standard atomic
    types without also having atomic load and store operations of the same
    size.˙ And while the 16-byte CAS can be used for loads and stores, it
    can't be used for 16-byte atomic loads on read-only memory.˙ This means
    that on older 64-bit x86 processors, 16-byte atomic types cannot be lock-free even if they support 16-byte CAS.

    Shit... Well, damn that kind of ruins things. Well, because I do not
    even need atomic loads/stores of the DWCAS (aka, two contiguous words)!

    Sigh. I just want C++ to have a always lock-free DWCAS on systems that
    support it. The loads and stores do not even need to be there. But, see,
    that is for my personal use. Sigh. Not good enough for the std?



    It seems that the libatomic implementation does, in some cases, use the CMPXCHG16B instruction rather than a mutex.
    Thank GOD! Then they can make their C++ impl report that this is always lock-free?

    The exact code run in the
    library depends on the cpuid of the processor, as well as compiler
    flags.˙ (The default "lowest common denominator" x86-64 instruction set
    does not support CMPXCHG16B.)

    So the C++ standard library atomics do not lead directly to the use of
    the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
    (or use a -march flag for a cpu family that supports it), then you can
    use the low-level builtin __sync_bool_compare_and_swap to generate it.

    Better still, in gcc 17 (in development) you can use the "-m128bit-
    atomic" flag to get full 16-byte atomics on processors for which the GCC folks have assurances from Intel or AMD that the 128-bit SSE2 vector
    loads and stores are atomic.˙ You can try this on godbolt.org.




    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 17:15:17 2026
    On 10/4/2026 2:34 AM, David Brown wrote:
    On 03/10/2026 23:12, Chris M. Thomasson wrote:
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code this >>>>> is ASM when the C/C++11 should already do it for me. That type of
    angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in gcc's
    C++ standard library, rather than a direct lock-free opcode.˙ I
    don't know the details of any of this - maybe there are flaws in
    some chips, maybe the single op-code actually takes longer than a
    lock- based implementation.˙ Maybe it's just that no one has got
    round to implementing it yet.

    I would guess the reason is consistent behaviour across different
    architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need it
    for then that would be okay. We need to step into the library call to
    find out.

    From what I have looked at, including the bugzilla link Joseph posted, there seem to be a number of complications involved.˙ A particular
    sticking point is that you can't have lock-free C++ standard atomic
    types without also having atomic load and store operations of the same
    size.˙ And while the 16-byte CAS can be used for loads and stores, it
    can't be used for 16-byte atomic loads on read-only memory.˙ This means
    that on older 64-bit x86 processors, 16-byte atomic types cannot be lock-free even if they support 16-byte CAS.

    It seems that the libatomic implementation does, in some cases, use the CMPXCHG16B instruction rather than a mutex.˙ The exact code run in the library depends on the cpuid of the processor, as well as compiler
    flags.˙ (The default "lowest common denominator" x86-64 instruction set
    does not support CMPXCHG16B.)

    So the C++ standard library atomics do not lead directly to the use of
    the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
    (or use a -march flag for a cpu family that supports it), then you can
    use the low-level builtin __sync_bool_compare_and_swap to generate it.

    Better still, in gcc 17 (in development) you can use the "-m128bit-
    atomic" flag to get full 16-byte atomics on processors for which the GCC folks have assurances from Intel or AMD that the 128-bit SSE2 vector
    loads and stores are atomic.˙ You can try this on godbolt.org.



    Well, see, just because I need to use DWCAS (LOCK CMPXCHG16B) on x64,
    for certain algos does not mean I want full double adjacent word loads
    and stores. That is besides the point.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 18:10:29 2026
    On 10/6/2026 8:19 PM, Bonita Montero wrote:
    Am 06.10.2026 um 22:18 schrieb Chris M. Thomasson:

    Afaict, you are into the internals of implementing a strong atomic
    refcount. ...

    You don't understand my idea - check the code.


    I am not sure if you understand your own idea. You talk about no RCU
    needed. Fine. A mutex does not need RCU as well. But you use CAS, loops,
    and futex. So, how can even begin to compare to RCU?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 18:53:52 2026
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:

    Not sure what you mean. Traditional RCU reads do not need any atomic
    RMW's and/or memory barriers (alpha aside for a moment).

    My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
    ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
    As long as the shared tshared_obj<T> doesn't change the updates are
    nearly for free. If the object changes the access is locked, but not
    with a separate futex; the pointer itself is the futex, meaning that
    it goes to -1 while there's a deferred update of the reference coun-
    ter the object points to. The pointer changes back to its initial
    value once the change has completed.
    The following diagram shows the micro-benchmark erformance of updating
    a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
    free. The atomic shared_ptr's update is up to more than 10.000 times
    slower:

    ˙˙˙˙https://ibb.co/nqGHv9pL

    This is the complete source:[...]

    Are you sure that cannot ever accidentally leak?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 8 18:55:45 2026
    On 10/5/2026 12:23 PM, Bonita Montero wrote:
    Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:[...]

    any problems with the incr call in tshared_obj(const shared_obj&), and m_pData->template incr<...>() for GCC?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Oct 9 08:14:51 2026
    Am 08.10.2026 um 21:19 schrieb Chris M. Thomasson:

    Well, your work sure seems to act like a proxy. But it uses CAS and
    futex. Therefore its out of scope with anything lock-free.

    You don't understand it.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Oct 9 08:16:00 2026
    Am 09.10.2026 um 03:10 schrieb Chris M. Thomasson:

    I am not sure if you understand your own idea. ...

    You keep missing the point, and then you say something like that ?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Oct 9 08:16:44 2026
    Am 08.10.2026 um 20:41 schrieb Chris M. Thomasson:

    I think you should use his atomic_ptr and be done with it. It pure lock- free. Yours is not: ...

    Sick !

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Oct 9 08:33:03 2026
    On 09/10/2026 02:13, Chris M. Thomasson wrote:
    On 10/4/2026 2:34 AM, David Brown wrote:
    On 03/10/2026 23:12, Chris M. Thomasson wrote:
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code
    this is ASM when the C/C++11 should already do it for me. That
    type of angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in
    gcc's C++ standard library, rather than a direct lock-free opcode. >>>>> I don't know the details of any of this - maybe there are flaws in
    some chips, maybe the single op-code actually takes longer than a
    lock- based implementation.˙ Maybe it's just that no one has got
    round to implementing it yet.

    I would guess the reason is consistent behaviour across different
    architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need it
    for then that would be okay. We need to step into the library call to
    find out.

    ˙From what I have looked at, including the bugzilla link Joseph
    posted, there seem to be a number of complications involved.˙ A
    particular sticking point is that you can't have lock-free C++
    standard atomic types without also having atomic load and store
    operations of the same size.˙ And while the 16-byte CAS can be used
    for loads and stores, it can't be used for 16-byte atomic loads on
    read-only memory.˙ This means that on older 64-bit x86 processors, 16-
    byte atomic types cannot be lock-free even if they support 16-byte CAS.

    Shit... Well, damn that kind of ruins things. Well, because I do not
    even need atomic loads/stores of the DWCAS (aka, two contiguous words)!

    Sigh. I just want C++ to have a always lock-free DWCAS on systems that support it. The loads and stores do not even need to be there. But, see, that is for my personal use. Sigh. Not good enough for the std?


    Standard libraries are not about individuals.

    "Always lock-free" means /always/ lock-free. If the target does not
    support lock-free versions of all the operations on the atomic type, the atomic type is not "always lock-free". (As far as I can tell, this is a limitation of the x86 processors, perhaps also partly a failure of AMD
    and Intel to document guarantees about atomic loads and stores. It is
    not a failure of the gcc C++ standard library.)



    It seems that the libatomic implementation does, in some cases, use
    the CMPXCHG16B instruction rather than a mutex.
    Thank GOD! Then they can make their C++ impl report that this is always lock-free?

    Or perhaps thank the libatomic authors and the GCC C++ standard library implementers for making a correct implementation?

    No, their library cannot say that 128-bit types are "always lock-free"
    unless they /are/ always lock-free. You can always test gcc 17 with the "-m128bit-atomic" flag, assuming you have an appropriate target
    processor (or just look at it on godbolt.org).


    The exact code run in the library depends on the cpuid of the
    processor, as well as compiler flags.˙ (The default "lowest common
    denominator" x86-64 instruction set does not support CMPXCHG16B.)

    So the C++ standard library atomics do not lead directly to the use of
    the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
    (or use a -march flag for a cpu family that supports it), then you can
    use the low-level builtin __sync_bool_compare_and_swap to generate it.

    Better still, in gcc 17 (in development) you can use the "-m128bit-
    atomic" flag to get full 16-byte atomics on processors for which the
    GCC folks have assurances from Intel or AMD that the 128-bit SSE2
    vector loads and stores are atomic.˙ You can try this on godbolt.org.





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Oct 9 11:05:26 2026
    On 10/8/2026 11:14 PM, Bonita Montero wrote:
    Am 08.10.2026 um 21:19 schrieb Chris M. Thomasson:

    Well, your work sure seems to act like a proxy. But it uses CAS and
    futex. Therefore its out of scope with anything lock-free.

    You don't understand it.

    Oh I do understand it. Actually, afaict, it "should" beat
    atomic<shared_ptr>. But, you need to run it through a race detector.
    Actually, the damn AI seemingly found some issues. Found no issues in my
    proxy collector. I already knew that because I had it running and
    passing various tests in Relacy way back in 2010. And for you to make
    claims that RCU isn't necessary is rather wild!

    Therefore, I say that you don't really understand RCU...

    Fwiw, here is a test harness it made for your work:


    ______________
    // Leak / lifetime harness for shared_obj / tshared_obj (his posted
    code, compile fixes only).
    // Every Cfg owns a heap Block; allocation and free counters make leaks
    and early frees visible.
    #include <atomic>
    #include <chrono>
    #include <cstdio>
    #include <thread>
    #include <vector>
    #include <csignal>
    #include <cstring>
    #include <unistd.h>
    #include <sys/wait.h>
    #include "his/shared_obj.hpp"

    static std::atomic<long> g_alloc(0), g_free(0);
    struct Block { std::uint64_t v; };
    struct Cfg
    {
    Block* p;
    explicit Cfg(std::uint64_t k) : p(new Block{k}) {
    g_alloc.fetch_add(1); }
    Cfg(Cfg&& o) noexcept : p(o.p) { o.p = nullptr; }
    Cfg(const Cfg&) = delete;
    ~Cfg() noexcept { if (p) { delete p; g_free.fetch_add(1); } }
    };

    using SO = shared_obj<Cfg, 1>;
    using TSO = tshared_obj<Cfg, 1>;

    static void reset() { g_alloc = 0; g_free = 0; }
    static void report(const char* name, bool bug)
    {
    std::printf(" allocated=%ld freed=%ld leaked=%ld -> %s\n\n",
    g_alloc.load(), g_free.load(), g_alloc.load() - g_free.load(),
    bug ? "BUG REPRODUCED" : "ok");
    (void)name;
    }

    // A: a reader that moves to a new published version never drops its
    reference to the old one.
    static void test_A()
    {
    std::printf("A. reader follows a republished pointer (slowAssign)\n");
    reset();
    {
    TSO tso( make_shared_obj<Cfg,1>(1) );
    SO local;
    local = tso; // holds v1
    tso = make_shared_obj<Cfg,1>(2); // writer publishes v2,
    drops tso's ref on v1
    local = tso; // reader moves to v2 --
    should release v1 here
    } // everything destroyed;
    v1 should be freed by now
    report("A", g_alloc.load() != g_free.load());
    }

    // B: assigning an empty shared_obj leaves the destination pointing at
    an object it no longer counts.
    static void test_B()
    {
    std::printf("B. a = empty, while another owner still holds the
    object (operator=(const shared_obj&))\n");
    reset();
    SO empty;
    SO c;
    bool a_nonnull_after = false;
    long freed_while_c_alive = 0;
    {
    SO a = make_shared_obj<Cfg,1>(7);
    c = a; // two owners, count == 2
    std::printf(" use_count before: %u\n", a.use_count());
    a = empty; // should make a empty,
    count == 1
    a_nonnull_after = (bool)a;
    std::printf(" after a = empty: a is %s, c.use_count() = %u (expected a empty, count 1)\n",
    a_nonnull_after ? "STILL NON-NULL" : "null", c.use_count());
    } // ~a runs: decrements again
    freed_while_c_alive = g_free.load();
    std::printf(" after ~a, c still alive: blocks freed = %ld
    (expected 0 -- c still owns it)\n", freed_while_c_alive);
    report("B", a_nonnull_after || freed_while_c_alive != 0);
    // c now dangles; do not touch it. Leak its (already freed) pointer deliberately:
    // destroying c would double free, so exit this scope by leaking it.
    new SO(std::move(c)); // intentional: avoid the double free for the demo
    }

    // C: tshared_obj = empty shared_obj dereferences null.
    static void test_C()
    {
    std::printf("C. tshared_obj = empty shared_obj (operator=(const shared_obj&))\n");
    pid_t pid = fork();
    if (pid == 0)
    {
    TSO tso( make_shared_obj<Cfg,1>(1) );
    SO empty;
    tso = empty; // so.m_pData->incr()
    with m_pData == nullptr
    _exit(0);
    }
    int st = 0;
    waitpid(pid, &st, 0);
    if (WIFSIGNALED(st))
    std::printf(" child died with signal %d (%s) -> BUG REPRODUCED\n\n", WTERMSIG(st), strsignal(WTERMSIG(st)));
    else
    std::printf(" child exited normally (%d) -> ok\n\n", WEXITSTATUS(st));
    }

    // D: concurrent readers polling while a writer republishes.
    static void test_D(int readers, int period_us, double secs)
    {
    std::printf("D. %d readers polling, one writer republishing every
    %d us for %.1fs\n", readers, period_us, secs);
    reset();
    std::atomic<bool> stop(false);
    std::atomic<unsigned long> versions(0);
    {
    TSO tso( make_shared_obj<Cfg,1>(1) );
    std::vector<std::thread> th;
    for (int i = 0; i < readers; ++i)
    th.emplace_back([&]{
    SO local;
    while (!stop.load(std::memory_order_relaxed)) local = tso;
    });
    std::thread w([&]{
    std::uint64_t k = 2;
    while (!stop.load(std::memory_order_relaxed)) {
    tso = make_shared_obj<Cfg,1>(k++);
    versions.fetch_add(1);

    std::this_thread::sleep_for(std::chrono::microseconds(period_us));
    }
    });
    std::this_thread::sleep_for(std::chrono::duration<double>(secs));
    stop = true;
    for (auto& t : th) t.join();
    w.join();
    }
    std::printf(" versions published=%lu\n", versions.load());
    report("D", g_alloc.load() != g_free.load());
    }

    int main()
    {
    test_A();
    test_B();
    test_C();
    test_D(3, 1000, 1.5);
    return 0;
    }
    ______________



    ______________
    #pragma once
    #include <algorithm> // added: std::max
    #include <cstdint> // added: uint32_t
    #include <cstddef> // added: size_t
    #include <atomic>
    #include <concepts>
    #include <type_traits>
    #include <cassert>
    #include "cacheline.hpp"
    #include "inline.hpp"

    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdangling-else"
    #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
    #endif

    template<typename T, int ThreadMode>
    struct shared_obj;

    template<typename T, int ThreadMode, typename ... Args>
    shared_obj<T, ThreadMode> make_shared_obj( Args &&... args );
    template<typename T, int ThreadMode>
    struct tshared_obj;

    template<typename T, int ThreadMode>
    struct shared_obj
    {
    shared_obj() noexcept = default;
    shared_obj( const tshared_obj<T, ThreadMode> & ) noexcept;
    shared_obj( tshared_obj<T, ThreadMode> && ) noexcept;
    template<typename T2>
    shared_obj( const shared_obj<T2, ThreadMode> &so ) noexcept;
    template<typename T2>
    shared_obj( shared_obj<T2, ThreadMode> &&so ) noexcept;
    shared_obj &operator =( const shared_obj & ) noexcept;
    shared_obj &operator =( shared_obj && ) noexcept;
    shared_obj &operator =( const tshared_obj<T, ThreadMode> & ) noexcept;
    shared_obj &operator =( tshared_obj<T, ThreadMode> && ) noexcept;
    ~shared_obj();
    T *get() const noexcept;
    operator bool() const noexcept;
    uint32_t use_count() const noexcept;
    T &operator *() const noexcept;
    T *operator ->() const noexcept;
    void reset() noexcept;
    private:
    static_assert( std::is_nothrow_destructible_v<T> );
    template<typename T2, int ThreadMode2, typename ... Args>
    friend shared_obj<T2, ThreadMode2> make_shared_obj( Args &&... args );
    template<typename T2, int ThreadMode2>
    friend struct tshared_obj;
    template<typename T2, int ThreadMode2>
    friend struct shared_obj;
    static constexpr size_t
    RcAlign = ThreadMode > 0 ? CL_SIZE : alignof( uint32_t ),
    TotalAlign = std::max( ThreadMode < 0 ? CL_SIZE : alignof( T ), RcAlign );
    struct alignas( RcAlign ) ref_count
    {
    uint32_t m_refCount;
    template<std::memory_order Order = std::memory_order_relaxed>
    void incr() noexcept;
    bool decr() noexcept;
    };
    struct non_class_value_type
    {
    T m_value;
    };
    static constexpr bool ValueIsClass = std::is_class_v<T>;
    using value_type = std::conditional_t<ValueIsClass, T, non_class_value_type>;
    struct alignas( TotalAlign ) data : ref_count, value_type
    {
    template<typename ... Args>
    data( Args &&... args );
    };
    shared_obj( data *obj ) noexcept;
    template<typename T2>
    data *assign( const shared_obj<T2, ThreadMode> & ) noexcept;
    data *m_pData = nullptr;
    void slowAssign( const tshared_obj<T, ThreadMode> &tso, data
    *pDtaRef ) noexcept;
    };

    template<typename T, int ThreadMode>
    template<std::memory_order Order>
    FORCEINLINE void shared_obj<T, ThreadMode>::ref_count::incr() noexcept
    {
    uint32_t before;
    if constexpr( ThreadMode )
    before = std::atomic_ref( m_refCount ).fetch_add( 1, Order );
    else
    before = m_refCount++;
    assert(before && before != (unsigned)-1);
    return (void)before;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE bool shared_obj<T, ThreadMode>::ref_count::decr() noexcept
    {
    if constexpr( ThreadMode )
    return std::atomic_ref( m_refCount ).fetch_sub( 1, std::memory_order_relaxed ) == 1;
    else
    return !--m_refCount;
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    inline shared_obj<T, ThreadMode>::shared_obj( const shared_obj<T2,
    ThreadMode> &so ) noexcept :
    m_pData( assign( so ) )
    {
    if( m_pData )
    m_pData->incr();
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    inline shared_obj<T, ThreadMode>::shared_obj( shared_obj<T2, ThreadMode>
    &&so ) noexcept :
    m_pData( assign( so ) )
    {
    so.m_pData = nullptr;
    }

    template<typename T, int ThreadMode>
    template<typename T2>
    FORCEINLINE auto shared_obj<T, ThreadMode>::assign( const shared_obj<T2, ThreadMode> &so ) noexcept -> data *
    {
    using namespace std;
    constexpr bool Polymorphic = !same_as<T, T2> && derived_from<T, T2>
    && is_polymorphic_v<T2>;
    static_assert( same_as<T, T2> || derived_from<T2, T> || Polymorphic );
    if( !so.m_pData )
    return nullptr;
    if constexpr( Polymorphic )
    if( !dynamic_cast<T *>( &so.m_pData->m_value ) )
    return nullptr;
    return (data *)so.m_pData;
    }

    template<typename T, int ThreadMode>
    template<typename ... Args>
    inline shared_obj<T, ThreadMode>::data::data( Args &&... args ) :
    ref_count( 1 ),
    value_type( T( std::forward<Args>( args ) ... ) )
    {
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =(
    shared_obj &&other ) noexcept
    {
    if( m_pData == other.m_pData ) [[unlikely]]
    {
    other.m_pData = nullptr;
    if( m_pData ) [[likely]]
    m_pData->decr();
    return *this;
    }
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = other.m_pData;
    other.m_pData = nullptr;
    return *this;
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =( const shared_obj &other ) noexcept
    {
    if( m_pData == other.m_pData )
    return *this;
    if( m_pData && m_pData->decr() )
    {
    delete m_pData;
    m_pData = nullptr;
    }
    if( !other.m_pData )
    return *this;
    m_pData = other.m_pData;
    m_pData->incr();
    return *this;
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode>::~shared_obj()
    {
    if( m_pData && m_pData->decr() )
    delete m_pData;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T *shared_obj<T, ThreadMode>::get() const noexcept
    {
    if constexpr( ValueIsClass )
    return static_cast<T *>( m_pData );
    else
    return &m_pData->m_value;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::operator bool() const noexcept
    {
    return (bool)m_pData;
    }

    template<typename T, int ThreadMode>
    uint32_t shared_obj<T, ThreadMode>::use_count() const noexcept
    {
    if( !m_pData )
    return 0;
    if constexpr( ThreadMode )
    return std::atomic_ref( m_pData->m_refCount ).load( std::memory_order_relaxed );
    else
    return m_pData->m_refCount;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( data *pData ) noexcept :
    m_pData( pData )
    {
    assert(!m_pData || m_pData->m_refCount);
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T &shared_obj<T, ThreadMode>::operator *() const noexcept
    {
    assert(m_pData);
    return *get();
    }

    template<typename T, int ThreadMode>
    FORCEINLINE T *shared_obj<T, ThreadMode>::operator ->() const noexcept
    {
    assert(m_pData);
    return get();
    }

    template<typename T, int ThreadMode>
    void shared_obj<T, ThreadMode>::reset() noexcept
    {
    if( !m_pData )
    return;
    if( m_pData->decr() )
    delete m_pData;
    m_pData = nullptr;
    }
    template<typename T, int ThreadMode, typename ... Args>
    shared_obj<T, ThreadMode> make_shared_obj( Args &&... args )
    {
    using data = shared_obj<T, ThreadMode>::data;
    return shared_obj<T, ThreadMode>( new data( std::forward<Args>(
    args ) ... ) );
    }

    template<typename T, int ThreadMode>
    struct tshared_obj
    {
    tshared_obj() noexcept = default;
    tshared_obj( const shared_obj<T, ThreadMode> & ) noexcept;
    tshared_obj( shared_obj<T, ThreadMode> && ) noexcept;
    ~tshared_obj();
    tshared_obj &operator =( const shared_obj<T, ThreadMode> & ) noexcept;
    tshared_obj &operator =( shared_obj<T, ThreadMode> && ) noexcept;
    void exchange( shared_obj<T, ThreadMode> &so );
    void reset() noexcept;
    private:
    static_assert( ThreadMode );
    using data = shared_obj<T, ThreadMode>::data;
    template<typename T2, int ThreadMode2>
    friend struct shared_obj;
    mutable std::atomic<data *> m_pAtmData = nullptr;
    };

    template<typename T, int ThreadMode>
    FORCEINLINE auto shared_obj<T, ThreadMode>::operator =( const
    tshared_obj<T, ThreadMode> &tso ) noexcept -> shared_obj &
    {
    data *pDtaRef = tso.m_pAtmData.load( std::memory_order_relaxed );
    if( m_pData != pDtaRef ) [[unlikely]]
    slowAssign( tso, pDtaRef );
    return *this;
    }

    template<typename T, int ThreadMode>
    NOINLINE void shared_obj<T, ThreadMode>::slowAssign( const
    tshared_obj<T, ThreadMode> &tso, data *pDtaRef ) noexcept
    {
    using namespace std;
    for( ; ; )
    {
    if( !pDtaRef )
    {
    data *pData = m_pData;
    if( pData && pData->decr() )
    delete pData;
    m_pData = nullptr;
    return;
    }
    if( pDtaRef == (data *)-1 )
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef, (data
    *)-1, memory_order_relaxed, memory_order_relaxed ) ) [[likely]]
    break;
    if( m_pData == pDtaRef )
    return;
    }
    m_pData = pDtaRef;
    m_pData->template incr<memory_order_acquire>(); // FIX (GCC):
    needs `template`
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    tso.m_pAtmData.notify_all();
    }

    template<typename T, int ThreadMode>
    shared_obj<T, ThreadMode> &shared_obj<T, ThreadMode>::operator =( tshared_obj<T, ThreadMode> &&tso ) noexcept
    {
    using namespace std;
    data *pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( !pDtaRef )
    {
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = nullptr;
    return *this;
    }
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    tso.m_pAtmData.wait( pDtaRef );
    pDtaRef = tso.m_pAtmData.load( memory_order_relaxed );
    }
    else if( tso.m_pAtmData.compare_exchange_strong( pDtaRef,
    nullptr, memory_order_acquire, memory_order_relaxed ) )
    break;
    if( m_pData && m_pData->decr() )
    delete m_pData;
    m_pData = pDtaRef;
    return *this;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( const tshared_obj<T, ThreadMode> &tso ) noexcept :
    m_pData( nullptr )
    {
    *this = tso;
    }

    template<typename T, int ThreadMode>
    FORCEINLINE shared_obj<T, ThreadMode>::shared_obj( tshared_obj<T,
    ThreadMode> &&tso ) noexcept :
    m_pData( nullptr )
    {
    *this = std::move( tso );
    }

    template<typename T, int ThreadMode>
    tshared_obj<T, ThreadMode>::tshared_obj( const shared_obj<T, ThreadMode>
    &so ) noexcept :
    m_pAtmData( so.m_pData )
    {
    if( so.m_pData )
    so.m_pData->template incr<std::memory_order_release>(); //
    FIX: posted as incr( std::memory_order_release )
    }

    template<typename T, int ThreadMode>
    inline tshared_obj<T, ThreadMode>::tshared_obj( shared_obj<T,
    ThreadMode> &&so ) noexcept :
    m_pAtmData( so.m_pData )
    {
    so.m_pData = nullptr;
    }

    template<typename T, int ThreadMode>
    tshared_obj<T, ThreadMode>::~tshared_obj()
    {
    data *pAtmData = m_pAtmData.load( std::memory_order_relaxed );
    if( pAtmData && pAtmData->decr() )
    delete pAtmData;
    }

    template<typename T, int ThreadMode>
    NOINLINE auto tshared_obj<T, ThreadMode>::operator =( const
    shared_obj<T, ThreadMode> &so ) noexcept -> tshared_obj &
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData ) [[likely]]
    return *this;
    else if( pDtaRef == (data *)-1 )
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, (data
    *)-1, memory_order_relaxed, memory_order_relaxed ) )
    break;
    if( pDtaRef && pDtaRef->decr() )
    delete pDtaRef;
    so.m_pData->incr();
    m_pAtmData.store( so.m_pData, memory_order_release );
    m_pAtmData.notify_all();
    return *this;
    }

    template<typename T, int ThreadMode>
    auto tshared_obj<T, ThreadMode>::operator =( shared_obj<T, ThreadMode>
    &&so ) noexcept -> tshared_obj &
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData )
    {
    if( !so.m_pData )
    return *this;
    so.m_pData->decr();
    so.m_pData = nullptr;
    return *this;
    }
    else if( m_pAtmData == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef,
    so.m_pData, memory_order_release, memory_order_relaxed ) )
    break;
    if( pDtaRef && pDtaRef->decr() )
    delete pDtaRef;
    so.m_pData = nullptr;
    return *this;
    }

    template<typename T, int ThreadMode>
    void tshared_obj<T, ThreadMode>::exchange( shared_obj<T, ThreadMode> &so )
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( pDtaRef == so.m_pData )
    return;
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef,
    so.m_pData, memory_order_acq_rel, memory_order_relaxed ) )
    break;
    so.m_pData = pDtaRef;
    }

    template<typename T, int ThreadMode>
    void tshared_obj<T, ThreadMode>::reset() noexcept
    {
    using namespace std;
    data *pDtaRef = m_pAtmData.load( memory_order_relaxed );
    for( ; ; )
    if( !pDtaRef )
    return;
    else if( pDtaRef == (data *)-1 ) [[unlikely]]
    {
    m_pAtmData.wait( pDtaRef );
    pDtaRef = m_pAtmData.load( memory_order_relaxed );
    }
    else if( m_pAtmData.compare_exchange_strong( pDtaRef, nullptr, memory_order_relaxed, memory_order_relaxed ) )
    break;
    if( pDtaRef->decr() )
    delete pDtaRef;
    }

    #if defined(__clang__)
    #pragma clang diagnostic pop
    #endif
    _____________


    Well, perhaps you can take a look at it and make sure to have some
    grains of salt. Actually, you should just port it over to relacy and run
    it.

    Actually, I am wondering. Can you run the AI test on your end? I don't
    have access to you missing code anyway. cacheline, inline, etc...

    The harness is leak_demo.cpp, and each test reproduced its bug on the
    first run.

    Test What it does Result
    A A reader polls, the writer republishes, the reader polls again, then everything is destroyed. 2 allocated, 1 freed, 1 leaked. The reader never released the old version.
    B a and c both own an object, then a = empty. a is still non-null afterward, and when a is destroyed the object is freed while c still
    owns it. This is a use-after-free setup.
    C tshared_obj = empty shared_obj, run in a forked child. Segmentation fault, from so.m_pData->incr() on null.
    D 3 readers polling while a writer republishes every 1 ms for 1.5s. 1,330 allocated, 1 freed, 1,329 leaked. Essentially every version is
    lost once readers have seen it.


    Well, did it find something?


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Oct 10 14:20:28 2026
    On 10/7/2026 8:56 PM, Bonita Montero wrote:
    Am 07.10.2026 um 22:13 schrieb Chris M. Thomasson:

    Huh? You are either making a strong atomic refcount or not.

    With RCU a newer version of the dater may have been published and a
    thread is working with an older version. With my idea it's the same.

    Afaict, you idea is ridden with bugs.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Sun Oct 11 06:59:20 2026
    Am 10.10.2026 um 23:20 schrieb Chris M. Thomasson:

    Afaict, you idea is ridden with bugs.

    I've written unit- and performance-tests.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Sun Oct 11 11:11:10 2026
    On 9/16/26 6:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something that's been around forever.*˙ It's simple enough that it must be in use somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    I threw together a really quick and dirty test to see how much
    overhead all those extra loads would cause. Doesn't appear to
    add any. I will have to add it to the proxies project testcase
    for more extensive testing but it will take a bit of work since
    all the other proxies implement BasicLock and consequential RCU
    readers do not.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 14:07:36 2026
    On 10/10/2026 9:59 PM, Bonita Montero wrote:
    Am 10.10.2026 um 23:20 schrieb Chris M. Thomasson:

    Afaict, you idea is ridden with bugs.

    I've written unit- and performance-tests.


    Have you ran them against any race detectors?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 14:10:24 2026
    On 10/11/2026 8:11 AM, Joseph Seigh wrote:
    On 9/16/26 6:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something
    that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    I threw together a really quick and dirty test to see how much
    overhead all those extra loads would cause.˙ Doesn't appear to
    add any.˙ I will have to add it to the proxies project testcase
    for more extensive testing but it will take a bit of work since
    all the other proxies implement BasicLock and consequential RCU
    readers do not.

    If you get some free time to burn, add my proxy to your test bench. Its
    simply not meant to beat fancy asymmetric RCU+SMR type algos, but it
    works in 100% std C++. Which is fairly decent.

    https://pastebin.com/raw/CYZ78gVj

    It should just compile right up and run.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Sun Oct 11 21:44:13 2026
    On 10/11/26 5:10 PM, Chris M. Thomasson wrote:


    If you get some free time to burn, add my proxy to your test bench. Its simply not meant to beat fancy asymmetric RCU+SMR type algos, but it
    works in 100% std C++. Which is fairly decent.

    https://pastebin.com/raw/CYZ78gVj

    It should just compile right up and run.

    SMRProxy read lock is just an atomic load and store w/o membar.
    I did a quick and dirty perf test of what would be the typical
    user space RCU read lock logic, incrementing a local counter.

    An atomic load, increment of loaded value, and atomic store
    of that value is basically just as fast as SMRProxy, despite
    that increment. An atomic increment is quite a bit slower
    on x86 since you have a full memory barrier there. You
    can't use asymmetric memory barriers to avoid that in this
    case.

    QSBR and EBR are basically the same thing. Grace periods are
    epochs. The only difference is the QSBR logic to decide what
    grace period each reader is referencing is a bit more indirect.
    You are looking at events rather than conditions. I didn't
    do a user space RCU implementation since I didn't want to
    deal with that extra logic. And there are enough user space
    RCU implementations out there already.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 20:04:12 2026
    On 10/11/2026 6:44 PM, Joseph Seigh wrote:
    On 10/11/26 5:10 PM, Chris M. Thomasson wrote:


    If you get some free time to burn, add my proxy to your test bench.
    Its simply not meant to beat fancy asymmetric RCU+SMR type algos, but
    it works in 100% std C++. Which is fairly decent.

    https://pastebin.com/raw/CYZ78gVj

    It should just compile right up and run.

    SMRProxy read lock is just an atomic load and store w/o membar.
    I did a quick and dirty perf test of what would be the typical
    user space RCU read lock logic, incrementing a local counter.

    I don't have any of my old tests anymore from 2003-2010 ish. Damn. It
    was fun to compare and contrast different proxys back in the day. My
    collector is always going to lose against a proper RCU, or SMRProxy,
    RCU-SMR hybrid. But, iirc, they can be useful for the writer side of the
    algo. Also, its fun to see them beat a read/write lock. Some of those
    tests where pretty lop sided where I had to terminate the read-write
    lock version because it was taking way too long. Remember when you first
    came up with the proxy using your atomic_ptr way back on c.p.t?

    Fwiw, I think your old tests measured reads per-thread per-second?


    An atomic load, increment of loaded value, and atomic store
    of that value is basically just as fast as SMRProxy, despite
    that increment.˙˙ An atomic increment is quite a bit slower
    on x86 since you have a full memory barrier there.˙ You
    can't use asymmetric memory barriers to avoid that in this
    case.

    Well, if your "crafty" there is a way to "amortize" it... But it
    involves a certain usage pattern. A user needs to be familiar with it so
    it tends to be a hard sell. Anyway, basically in my proxy, or any proxy,
    I think.

    try_to_iterate just tries to iterate a linked structure or something. If
    its empty it returns false. Otherwise, true:
    _______________
    collector& c = proxy_acquire();

    for (unsigned long i = 1 ;; ++i)
    {
    if (! try_to_iterate(c, shared_lock_free_list))
    {
    break;
    }

    if (! (i % 4096))
    {
    c = proxy_sync(c);
    }
    }

    proxy_release(c);
    _______________

    basically, proxy_sync is the following function in my old code:
    _______________
    collector& sync(collector& c)
    {
    // check if the `c' is in the middle of a quiescence process.
    if (c.m_count.load(std::memory_order_relaxed) & ct_proxy_quiescent)
    {
    // drop `c' and get the next collector.
    release(c);

    return acquire();
    }

    return c;
    }
    _______________

    So, it actually works way better when its under load! Funny.


    QSBR and EBR are basically the same thing.˙ Grace periods are
    epochs.˙ The only difference is the QSBR logic to decide what
    grace period each reader is referencing is a bit more indirect.
    You are looking at events rather than conditions.˙ I didn't
    do a user space RCU implementation since I didn't want to
    deal with that extra logic.˙ And there are enough user space
    RCU implementations out there already.

    Yeah. I just always liked my single word proxy. Its nice that its 100% portable wrt std C++. I remember your first one using atomic_ptr. Iirc,
    it had the backlink and called dtors recursively. Fwiw, mine old proxy
    just calls:
    _______________
    static std::uint32_t prv_destroy(ct_node* n)
    {
    std::uint32_t count = 0;

    while (n)
    {
    ct_node* next = n->m_defer_next;
    delete n;
    count++;
    n = next;
    }

    return count;
    }
    _______________


    Sometimes I feel like getting back into these. Too busy with vector
    fields and fractals now. Having a lot of fun with compute and frag
    shaders, etc...

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 20:07:12 2026
    On 10/11/2026 8:04 PM, Chris M. Thomasson wrote:
    On 10/11/2026 6:44 PM, Joseph Seigh wrote:
    On 10/11/26 5:10 PM, Chris M. Thomasson wrote:
    [...]
    Sometimes I feel like getting back into these. Too busy with vector
    fields and fractals now. Having a lot of fun with compute and frag
    shaders, etc...

    Actually, I need to find out if the current Relacy can simulate async
    membars, akin to FlushProcessWriteBuffers. If it can, wow! I should just
    ask Dmitriy. Have not conversed with him for a while.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Mon Oct 12 05:18:45 2026
    Am 11.10.2026 um 23:07 schrieb Chris M. Thomasson:

    Have you ran them against any race detectors?

    I've ran the code with a competition of 32 threads.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 20:31:23 2026
    On 10/11/2026 8:18 PM, Bonita Montero wrote:

    I've ran the code with a competition of 32 threads.

    Nice, 32 threads is a good load! Actually, can you tell me a bit more
    about what the test did? Mainly... did the shared pointer actually
    change during the run (and how often), and did anything check the
    results at the end, like total allocated vs. total freed?

    The reason I ask is that the trouble spots are in the paths that only
    run when the pointer changes, and a run that never changes it can come
    out clean without exercising them. Also curious whether you tried it
    under ThreadSanitizer or ASan/LeakSanitizer, since even one run there
    would tell us a lot.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Oct 11 20:34:24 2026
    On 10/11/2026 8:07 PM, Chris M. Thomasson wrote:
    On 10/11/2026 8:04 PM, Chris M. Thomasson wrote:
    On 10/11/2026 6:44 PM, Joseph Seigh wrote:
    On 10/11/26 5:10 PM, Chris M. Thomasson wrote:
    [...]
    Sometimes I feel like getting back into these. Too busy with vector
    fields and fractals now. Having a lot of fun with compute and frag
    shaders, etc...

    Actually, I need to find out if the current Relacy can simulate async membars, akin to FlushProcessWriteBuffers. If it can, wow! I should just
    ask Dmitriy. Have not conversed with him for a while.

    HEY NOW!!!! Relacy has systemwide_fence!

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Mon Oct 12 06:32:31 2026
    Am 12.10.2026 um 05:31 schrieb Chris M. Thomasson:

    Nice, 32 threads is a good load! Actually, can you tell me a bit more
    about what the test did? Mainly... did the shared pointer actually
    change during the run (and how often), and did anything check the
    results at the end, like total allocated vs. total freed?
    static void timedTest( char const *what, auto fn, bool forTbl );

    static void soTest( int type, bool forTbl )
    {
    constexpr sobj_mode Mode = sobj_mode::Hdr;
    tshared_obj<string, Mode> tsoStr( make_shared_obj<string, Mode>( "hello
    world" ) );
    shared_obj<string, Mode> altStrs[2];
    if( type == StoreAlt )
    {
    altStrs[0] = make_shared_obj<string, Mode>( "alt1" );
    altStrs[1] = make_shared_obj<string, Mode>( "alt2" );
    }
    constexpr const char *Descr = "shared_obj: ";
    switch( type )
    {
    case Load:
    timedTest( Descr, [&]
    {
    thread_local shared_obj<string, Mode> soStr;
    soStr = tsoStr;
    }, forTbl );
    break;
    case Store:
    timedTest( Descr, [&]
    {
    thread_local shared_obj<string, Mode> soStr = tsoStr;
    tsoStr = soStr;
    }, forTbl );
    break;
    case StoreAlt:
    timedTest( Descr, [&]
    {
    thread_local bool flip = true;
    tsoStr = altStrs[flip = !flip];
    }, forTbl );
    break;
    case Xchg:
    timedTest( Descr, [&]
    {
    thread_local shared_obj<string, Mode> soStr = tsoStr;
    tsoStr.exchange( soStr );
    }, forTbl );
    break;
    case Move:
    timedTest( Descr, [&]
    {
    thread_local shared_obj<string, Mode> soStr;
    if( !soStr )
    soStr = move( tsoStr );
    else
    tsoStr = move( soStr );
    }, forTbl );
    }
    }

    static void timedTest( char const *what, auto fn, bool forTbl )
    {
    cout << what << endl;
    vector<jthread> threads;
    unsigned hc = jthread::hardware_concurrency();
    threads.reserve( hc );
    for( unsigned nThreads = 1; nThreads <= hc; ++nThreads )
    {
    latch lat( nThreads );
    atomic_int64_t tSum( 0 ), roundsSum = 0;
    atomic_bool stop( false );
    for( unsigned t = 1; t <= nThreads; ++t )
    {
    threads.emplace_back( [&, fn = fn]()
    {
    lat.arrive_and_wait();
    auto start = high_resolution_clock::now();
    int64_t rnd = 0;
    for( ; !stop.load( memory_order_relaxed ); ++rnd )
    fn();
    tSum += duration_cast<nanoseconds>( high_resolution_clock::now() -
    start ).count();
    roundsSum += rnd;
    } );
    #if defined(_WIN32)
    SetThreadAffinityMask( threads.back().native_handle(), (DWORD_PTR)1
    << (t - 1) );
    #endif
    }
    #if defined(NDEBUG)
    constexpr duration TIME = 100ms;
    #else
    constexpr duration TIME = 100ms;
    #endif
    this_thread::sleep_for( TIME );
    stop = true;
    threads.resize( 0 );
    double nsOp = (double)tSum / (double)roundsSum;
    string result = (ostringstream() << nsOp).str();
    replace_if( result.begin(), result.end(), []( char c ) { return c ==
    '.'; }, ',' );
    if( !forTbl )
    cout << "\t" << nThreads << ": " << nsOp << endl;
    else
    cout << "\"" << result<< "\"" << endl;
    }
    }

    But I'm pretty sure you don't understand the code since you didn't
    understand the basic idea.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Oct 12 12:58:03 2026
    On 10/8/2026 11:33 PM, David Brown wrote:
    On 09/10/2026 02:13, Chris M. Thomasson wrote:
    On 10/4/2026 2:34 AM, David Brown wrote:
    On 03/10/2026 23:12, Chris M. Thomasson wrote:
    On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
    On Sat, 3 Oct 2026 14:19:56 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/10/2026 20:13, Chris M. Thomasson wrote:
    It should be something akin to, why do I feel the need to code
    this is ASM when the C/C++11 should already do it for me. That
    type of angle?

    Something along these lines should be fine.˙ There may be some
    underlying reason why this is implemented as a library call in
    gcc's C++ standard library, rather than a direct lock-free opcode. >>>>>> I don't know the details of any of this - maybe there are flaws in >>>>>> some chips, maybe the single op-code actually takes longer than a >>>>>> lock- based implementation.˙ Maybe it's just that no one has got
    round to implementing it yet.

    I would guess the reason is consistent behaviour across different
    architectures.


    Well, if the library call goes to LOCK CMPXCHG16B for what we need
    it for then that would be okay. We need to step into the library
    call to find out.

    ˙From what I have looked at, including the bugzilla link Joseph
    posted, there seem to be a number of complications involved.˙ A
    particular sticking point is that you can't have lock-free C++
    standard atomic types without also having atomic load and store
    operations of the same size.˙ And while the 16-byte CAS can be used
    for loads and stores, it can't be used for 16-byte atomic loads on
    read-only memory.˙ This means that on older 64-bit x86 processors,
    16- byte atomic types cannot be lock-free even if they support 16-
    byte CAS.

    Shit... Well, damn that kind of ruins things. Well, because I do not
    even need atomic loads/stores of the DWCAS (aka, two contiguous words)!

    Sigh. I just want C++ to have a always lock-free DWCAS on systems that
    support it. The loads and stores do not even need to be there. But,
    see, that is for my personal use. Sigh. Not good enough for the std?


    Standard libraries are not about individuals.

    "Always lock-free" means /always/ lock-free.˙ If the target does not
    support lock-free versions of all the operations on the atomic type, the atomic type is not "always lock-free".˙ (As far as I can tell, this is a limitation of the x86 processors, perhaps also partly a failure of AMD
    and Intel to document guarantees about atomic loads and stores.˙ It is
    not a failure of the gcc C++ standard library.)

    I understand. I am only interested in if a fallback to a library, or
    whatever, uses the LOCK CMPXCHG16B on x64. Or any other arch if they
    support a native double-width CAS (DWCAS) instruction. LL/SC aside for a moment... It might be interesting if I can infer if say DWCAS was always lock-free but DW atomic loads/stores are not. That is fine with me.


    It seems that the libatomic implementation does, in some cases, use
    the CMPXCHG16B instruction rather than a mutex.
    Thank GOD! Then they can make their C++ impl report that this is
    always lock-free?

    Or perhaps thank the libatomic authors and the GCC C++ standard library implementers for making a correct implementation?

    Ponder on those implementers as akin to a "god"... lol. ;^)

    Way before C++11, implementing these things in per-arch ASM was my gold
    std. LTO aside for a moment... Uggg. I was scared/paranoid of a compiler trying to do some funny business with my results (.o) files, from GAS/MASM.


    No, their library cannot say that 128-bit types are "always lock-free" unless they /are/ always lock-free.˙ You can always test gcc 17 with the "-m128bit-atomic" flag, assuming you have an appropriate target
    processor (or just look at it on godbolt.org).

    Indeed.


    The exact code run in the library depends on the cpuid of the
    processor, as well as compiler flags.˙ (The default "lowest common
    denominator" x86-64 instruction set does not support CMPXCHG16B.)

    So the C++ standard library atomics do not lead directly to the use
    of the lock-free instruction.˙ But if you have the "-mxc16" compiler
    flag (or use a -march flag for a cpu family that supports it), then
    you can use the low-level builtin __sync_bool_compare_and_swap to
    generate it.

    Better still, in gcc 17 (in development) you can use the "-m128bit-
    atomic" flag to get full 16-byte atomics on processors for which the
    GCC folks have assurances from Intel or AMD that the 128-bit SSE2
    vector loads and stores are atomic.˙ You can try this on godbolt.org.

    Shit happens. This is a rather niche area anyway.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Oct 12 13:14:15 2026
    On 10/11/2026 9:32 PM, Bonita Montero wrote:
    static void timedTest( char const *what, auto fn, bool forTbl );

    [...]
    But I'm pretty sure you don't understand the code since you didn't
    understand the basic idea.

    Sigh.

    Your Load, Store and Xchg tests never change the pointer, so they only
    measure the fast path. Could you add one where a writer keeps swapping
    the slot while the others poll it, and print allocations vs. frees at
    the end?

    one thread republishing through StoreAlt while the others run Load, plus
    an allocated-versus-freed check at the end. That shows the herd on the
    lock and any leak in one run.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Oct 12 13:19:56 2026
    On 10/11/2026 9:32 PM, Bonita Montero wrote:
    Am 12.10.2026 um 05:31 schrieb Chris M. Thomasson:

    Nice, 32 threads is a good load! Actually, can you tell me a bit more
    about what the test did? Mainly... did the shared pointer actually
    change during the run (and how often), and did anything check the
    results at the end, like total allocated vs. total freed?
    [...]

    Where is sobj_mode defined, and is that the same shared_obj header you
    posted before?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Oct 12 13:24:59 2026
    On 10/12/2026 1:19 PM, Chris M. Thomasson wrote:
    On 10/11/2026 9:32 PM, Bonita Montero wrote:
    Am 12.10.2026 um 05:31 schrieb Chris M. Thomasson:

    Nice, 32 threads is a good load! Actually, can you tell me a bit more
    about what the test did? Mainly... did the shared pointer actually
    change during the run (and how often), and did anything check the
    results at the end, like total allocated vs. total freed?
    [...]

    Where is sobj_mode defined, and is that the same shared_obj header you posted before?

    Just use your thing to allow iterations of a lock-free linked list not
    have to use DWCAS, ABA. Make it single word. Let reader threads iterate
    it while writer threads are mutating it. Make it all lock-free, no
    loops. Can you do it as is?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Oct 13 08:14:15 2026
    Am 12.10.2026 um 22:14 schrieb Chris M. Thomasson:

    Your Load, Store and Xchg tests never change the pointer, so they only measure the fast path. ...

    For some tests, yes, but not all.

    one thread republishing through StoreAlt while the others run Load, ...

    No, all threads do the same.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 00:08:20 2026
    On 10/12/2026 11:14 PM, Bonita Montero wrote:
    Am 12.10.2026 um 22:14 schrieb Chris M. Thomasson:

    Your Load, Store and Xchg tests never change the pointer, so they only
    measure the fast path. ...

    For some tests, yes, but not all.

    one thread republishing through StoreAlt while the others run Load, ...

    No, all threads do the same.


    Try to make it uses say, reader and writer threads. Multiple readers
    iterating linked dynamic data structures while the writers are mutating
    them all at the same time.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 00:10:06 2026
    On 10/13/2026 12:08 AM, Chris M. Thomasson wrote:
    On 10/12/2026 11:14 PM, Bonita Montero wrote:
    Am 12.10.2026 um 22:14 schrieb Chris M. Thomasson:

    Your Load, Store and Xchg tests never change the pointer, so they
    only measure the fast path. ...

    For some tests, yes, but not all.

    one thread republishing through StoreAlt while the others run Load, ...

    No, all threads do the same.


    Try to make it uses say, reader and writer threads. Multiple readers iterating linked dynamic data structures while the writers are mutating
    them all at the same time.

    You may find that your futex is going be put under some stress in that scenario?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Oct 13 09:43:23 2026
    Am 13.10.2026 um 09:10 schrieb Chris M. Thomasson:

    You may find that your futex is going be put under some stress in that scenario?

    My code is optimized for RCU-like patterns.
    For all other operations futex'd synchronization is inevitable.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Tue Oct 13 11:33:34 2026
    Am 12.10.2026 um 22:19 schrieb Chris M. Thomasson:

    Where is sobj_mode defined, and is that the same shared_obj header you posted before?

    No, the ThreadMode was < 0 if the compete object including the
    reference counter was cacheline aligned, > 0 if the reference
    counter itself is the only aligned object and == 0 if there is
    no aligned. The latter case is if you want to use the shared_obj
    class itself for thread local shared objects. The tshared_obj
    requires "ThreadMode" be != 0.
    Now I have a separate sobj_mode enum:

    enum class sobj_mode : int { All = -1, No = 0, Hdr = 1 };

    ---

    #pragma once
    #include <atomic>
    #include <concepts>
    #include <type_traits>
    #include <cassert>
    #include "cacheline.hpp"
    #include "inline.hpp"

    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdangling-else"
    #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
    #endif

    enum class sobj_mode : int { All = -1, No = 0, Hdr = 1 };

    template<typename T, sobj_mode ThreadMode>
    struct shared_obj;

    template<typename T, sobj_mode ThreadMode, typename ... Args>
    shared_obj<T, ThreadMode> make_shared_obj( Args &&... args );
    template<typename T, sobj_mode ThreadMode>
    struct tshared_obj;

    template<typename T, sobj_mode ThreadMode>
    struct shared_obj
    {
    shared_obj() noexcept = default;
    shared_obj( const tshared_obj<T, ThreadMode> & ) noexcept;
    shared_obj( tshared_obj<T, ThreadMode> && ) noexcept;
    template<typename T2>
    shared_obj( const shared_obj<T2, ThreadMode> &so ) noexcept;
    template<typename T2>
    shared_obj( shared_obj<T2, ThreadMode
  • From Joseph Seigh@3:633/10 to All on Tue Oct 13 13:01:21 2026
    On 10/11/26 11:11 AM, Joseph Seigh wrote:
    On 9/16/26 6:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something
    that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    I threw together a really quick and dirty test to see how much
    overhead all those extra loads would cause.˙ Doesn't appear to
    add any.˙ I will have to add it to the proxies project testcase
    for more extensive testing but it will take a bit of work since
    all the other proxies implement BasicLock and consequential RCU
    readers do not.

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    std::atomic_thread_fence(std::memory_order_acquire);
    return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    mark = cseq_count.load(std::memory_order_acquire);
    node_t * item = list.load(std::memory_order_acquire);

    for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    item = item->next.load(std::memory_order_acquire);
    if (cseq_retry(mark, cseq_count)) [[unlikely]]
    goto retry;
    }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 12:29:11 2026
    On 10/13/2026 12:43 AM, Bonita Montero wrote:
    Am 13.10.2026 um 09:10 schrieb Chris M. Thomasson:

    You may find that your futex is going be put under some stress in that
    scenario?

    My code is optimized for RCU-like patterns.

    So it should work fine with readers iterating a shared lock-free linked
    list while writers are mutating said list.


    For all other operations futex'd synchronization is inevitable.

    I think you might have a problem in your following logic:

    m_pData = pDtaRef;
    m_pData->template incr<memory_order_acquire>();
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed ); tso.m_pAtmData.notify_all();

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 12:36:23 2026
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:
    On 10/11/26 11:11 AM, Joseph Seigh wrote:
    On 9/16/26 6:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    I threw together a really quick and dirty test to see how much
    overhead all those extra loads would cause.˙ Doesn't appear to
    add any.˙ I will have to add it to the proxies project testcase
    for more extensive testing but it will take a bit of work since
    all the other proxies implement BasicLock and consequential RCU
    readers do not.

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> & sequence)
    {
    ˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    It seems like cseq_retry on "every" iteration with its, two?, #LoadStore
    | #LoadLoad barriers is a bit much. Now that I have Relacy working fine
    on my end, and it has all I need for simulating even advanced RCU+SMR
    algos. Well, I will get back into it from time to time. Actually, can
    you get it working? Its a neat way to simulate your sync experiments.

    Wait I count an acquire on the item->next.load and then two acquires
    from cseq_retry? Am I missing something critical here Joe?

    https://github.com/dvyukov/relacy

    Its header only.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Tue Oct 13 19:34:55 2026
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    It seems like cseq_retry on "every" iteration with its, two?, #LoadStore
    | #LoadLoad barriers is a bit much. Now that I have Relacy working fine
    on my end, and it has all I need for simulating even advanced RCU+SMR
    algos. Well, I will get back into it from time to time. Actually, can
    you get it working? Its a neat way to simulate your sync experiments.

    Wait I count an acquire on the item->next.load and then two acquires
    from cseq_retry? Am I missing something critical here Joe?

    It would appear that way. That's what quick and dirty means. :)
    If the redundant load/load gets take out then there needs to be
    some other mechanism or documentation to ensure the loads are in
    order.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 16:46:21 2026
    On 10/13/2026 4:34 PM, Joseph Seigh wrote:
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    It seems like cseq_retry on "every" iteration with its, two?,
    #LoadStore | #LoadLoad barriers is a bit much. Now that I have Relacy
    working fine on my end, and it has all I need for simulating even
    advanced RCU+SMR algos. Well, I will get back into it from time to
    time. Actually, can you get it working? Its a neat way to simulate
    your sync experiments.

    Wait I count an acquire on the item->next.load and then two acquires
    from cseq_retry? Am I missing something critical here Joe?

    It would appear that way.˙˙ That's what quick and dirty means. :)
    If the redundant load/load gets take out then there needs to be
    some other mechanism or documentation to ensure the loads are in
    order.



    Shit. Yeah... Well, humm... For some damn reason Joe, it kinda sorta
    makes me ponder on why Relacy experiment goes "berserk" if I remove the consume membar from my smr_load: I sent an email to Dmitriy about it.

    I need to start getting dinner ready right now, but I am a bit baffled
    by it:

    ___________________
    per_node*
    smr_load(
    unsigned long tidx
    ) {
    for (;;)
    {
    per_node* node0 = m_node.load(rl::mo_relaxed, $);

    if (! node0)
    {
    RL_ASSERT(0);
    break;
    }

    m_threads[tidx].m_haz_0.store(node0, rl::mo_relaxed, $);

    // actually, iirc, a single seq_cst right here should work?

    per_node* node1 = m_node.load(rl::mo_consume, $);

    if (node0 == node1)
    {
    return node0;
    }
    }

    return nullptr;
    }
    ___________________



    my poller uses the systemwide_fence to get the async working:

    ________________________
    struct poller
    {
    rl::mutex m_poll;
    VAR_T(per_node*) m_defer0 = nullptr;

    void
    prv_poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    per_node* head = defer;
    defer = nullptr; // Take ownership of the caller's local batch

    // Merge any previously deferred nodes from m_defer0 into the processing batch
    per_node* curr = VAR(m_defer0);
    VAR(m_defer0) = nullptr;
    while (curr)
    {
    per_node* next_node = VAR(curr->m_defer);
    VAR(curr->m_defer) = head;
    head = curr;
    curr = next_node;
    }

    // Two-Pass Hazard Pointer Scan:
    // Pass 1: Capture all thread hazard slots
    std::vector<per_node*> haz_pass1(threads.size());
    for (size_t i = 0; i < threads.size(); ++i)
    {
    haz_pass1[i] = threads[i].m_haz_0.load(rl::mo_relaxed, $);
    }

    // Synchronization barrier between scans
    rl::systemwide_fence($);

    // Pass 2: Capture all thread hazard slots again
    std::vector<per_node*> haz_pass2(threads.size());
    for (size_t i = 0; i < threads.size(); ++i)
    {
    haz_pass2[i] = threads[i].m_haz_0.load(rl::mo_relaxed, $);
    }

    per_node* remaining = nullptr;

    while (head)
    {
    per_node* next = VAR(head->m_defer);
    bool protected_node = false;

    // Check if the node is protected in either pass snapshot
    for (auto haz : haz_pass1)
    {
    if (haz == head)
    {
    protected_node = true;
    break;
    }
    }

    if (!protected_node)
    {
    for (auto haz : haz_pass2)
    {
    if (haz == head)
    {
    protected_node = true;
    break;
    }
    }
    }

    if (protected_node)
    {
    // Keep it in the deferred list
    VAR(head->m_defer) = remaining;
    remaining = head;
    }
    else
    {
    // Safe to reclaim!
    delete head;
    }

    head = next;
    }

    // Store any still-protected nodes back into the central poller
    queue
    VAR(m_defer0) = remaining;
    }

    bool
    try_poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    if (m_poll.try_lock($))
    {
    prv_poll(defer, threads);
    m_poll.unlock($);
    return true;
    }

    return false;
    }

    void
    poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    m_poll.lock($);
    prv_poll(defer, threads);
    m_poll.unlock($);
    }
    };
    ________________________


    Two passes with the async member in between them.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Oct 14 05:56:00 2026
    Am 13.10.2026 um 21:29 schrieb Chris M. Thomasson:

    m_pData = pDtaRef;
    m_pData->template incr<memory_order_acquire>();
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed ); tso.m_pAtmData.notify_all();

    No, the code is fine.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 14 14:02:16 2026
    On 10/13/2026 8:56 PM, Bonita Montero wrote:
    Am 13.10.2026 um 21:29 schrieb Chris M. Thomasson:

    m_pData = pDtaRef;
    m_pData->template incr<memory_order_acquire>();
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    tso.m_pAtmData.notify_all();

    No, the code is fine.


    It does not need to dec any prior reference before:

    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );

    I must be missing something here. Thanks for your patience.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 14 14:16:18 2026
    On 10/13/2026 4:34 PM, Joseph Seigh wrote:
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.
    [...]

    Its interesting. Btw, I don't even need traditional SMR logic! This
    works fine:
    ________________
    per_node*
    smr_load(
    unsigned long tidx
    ) {
    per_node* node0 = m_node.load(rl::mo_relaxed, $);
    m_threads[tidx].m_haz_0.store(node0, rl::mo_relaxed, $);
    return node0;
    }
    ________________







    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Oct 14 14:46:49 2026
    On 10/14/2026 2:16 PM, Chris M. Thomasson wrote:
    On 10/13/2026 4:34 PM, Joseph Seigh wrote:
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.
    [...]

    Its interesting. Btw, I don't even need traditional SMR logic! This
    works fine:
    ________________
    per_node*
    smr_load(
    ˙˙˙ unsigned long tidx
    ) {
    ˙˙˙ per_node* node0 = m_node.load(rl::mo_relaxed, $);
    ˙˙˙ m_threads[tidx].m_haz_0.store(node0, rl::mo_relaxed, $);
    ˙˙˙ return node0;
    }
    ________________
    Oh OUCH! Sorry Joe. This does not work in Relacy. I forgot to run that
    test in my ct_main.cpp launcher. So, god damn it. I still need a consume barrier, or I think C++ fold that into acquire right?

    per_node*
    smr_load(
    unsigned long tidx
    ) {
    for (;;)
    {
    per_node* node0 = m_node.load(rl::mo_relaxed, $);

    if (! node0)
    {
    RL_ASSERT(0); // hould never happen in this test!
    m_threads[tidx].m_haz_0.store(nullptr, rl::mo_relaxed, $);
    break;
    }

    m_threads[tidx].m_haz_0.store(node0, rl::mo_relaxed, $);

    // actually, iirc, a single seq_cst right here should work?
    // well, this is an async test...

    per_node* node1 = m_node.load(rl::mo_consume, $);

    // uncomment below, Relacy goes berserker!
    //per_node* node1 = m_node.load(rl::mo_relaxed, $);
    if (node0 == node1)
    {
    return node0;
    }
    }

    return nullptr;
    }

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 15 11:23:15 2026
    On 10/13/2026 4:34 PM, Joseph Seigh wrote:
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.
    [...]


    std::atomic_thread_fence(std::memory_order_acquire);
    return mark != sequence.load(std::memory_order_acquire);

    Sometimes I get nervous wrt how c++ deals with integrating the
    standalone fence with the per atomic fences. Humm... As you know I
    recently got back into relacy. Its interesting to work with. When you
    get really bored, and have nothing to do, give it a go! Sigh. Might be
    fun for you?

    I should try to model your cseq_retry. Anyway, after talking to some
    people about the most recent Relacy, here is what we came up with.
    Apparently this is the most "relaxed" version of my async SMR test. I am
    not sure how to make it any more relaxed! Notice smr_load, smr_unload,
    and the prv_poll functions if you have the time and the interest.

    smr_unload cannot use relaxed, grrr!!!. It needs the release to allow
    C++ mem model to get into the second pass of the smr polling logic. So,
    pass 1, relaxed sample, systemwide_fence, then the second sample needs acquire:


    ____________________
    #pragma once

    #include <relacy/relacy.hpp>
    #include <cstdio>
    #include <cstddef>
    #include <cstdlib>

    #include <array>
    #include <string>
    #include <vector>

    namespace ct
    {
    namespace smr_test
    {
    constexpr unsigned long CT_THREADS_READERS = 5;
    constexpr unsigned long CT_THREADS_WRITERS = 3;
    constexpr unsigned long CT_THREADS = CT_THREADS_READERS + CT_THREADS_WRITERS;
    constexpr unsigned long CT_THREADS_READER_ITER_N = 12;
    constexpr unsigned long CT_THREADS_WRITER_ITER_N = 8;

    struct per_node
    {
    VAR_T(per_node*) m_defer = { nullptr };
    VAR_T(unsigned long) m_data = { 0 };
    };

    struct per_thread
    {
    rl::atomic<per_node*> m_haz_0 = { nullptr };
    };

    struct poller
    {
    rl::mutex m_poll;
    VAR_T(per_node*) m_defer0 = nullptr;

    void
    prv_poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    per_node* head = defer;
    defer = nullptr; // Take ownership of the caller's
    local batch

    // Merge any previously deferred nodes from m_defer0
    into the processing batch
    per_node* curr = VAR(m_defer0);
    VAR(m_defer0) = nullptr;
    while (curr)
    {
    per_node* next_node = VAR(curr->m_defer);
    VAR(curr->m_defer) = head;
    head = curr;
    curr = next_node;
    }

    // Two-Pass Hazard Pointer Scan:
    // Pass 1: Capture all thread hazard slots
    std::vector<per_node*> haz_pass1(threads.size());
    for (size_t i = 0; i < threads.size(); ++i)
    {
    haz_pass1[i] =
    threads[i].m_haz_0.load(rl::mo_relaxed, $);
    }

    // Synchronization barrier between scans
    rl::systemwide_fence($);

    // Pass 2: Capture all thread hazard slots again
    std::vector<per_node*> haz_pass2(threads.size());
    for (size_t i = 0; i < threads.size(); ++i)
    {
    haz_pass2[i] =
    threads[i].m_haz_0.load(rl::mo_acquire, $);
    }

    per_node* remaining = nullptr;

    while (head)
    {
    per_node* next = VAR(head->m_defer);
    bool protected_node = false;

    // Check if the node is protected in either pass
    snapshot
    for (auto haz : haz_pass1)
    {
    if (haz == head)
    {
    protected_node = true;
    break;
    }
    }

    if (! protected_node)
    {
    for (auto haz : haz_pass2)
    {
    if (haz == head)
    {
    protected_node = true;
    break;
    }
    }
    }

    if (protected_node)
    {
    // Keep it in the deferred list
    VAR(head->m_defer) = remaining;
    remaining = head;
    }
    else
    {
    // Safe to reclaim!
    VAR(head->m_data) = 0xDEADBEEF;
    delete head;
    }

    head = next;
    }

    // Store any still-protected nodes back into the
    central poller queue
    VAR(m_defer0) = remaining;
    }

    bool
    try_poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    if (m_poll.try_lock($))
    {
    prv_poll(defer, threads);
    m_poll.unlock($);
    return true;
    }

    return false;
    }

    void
    poll(
    per_node*& defer,
    std::array<per_thread, CT_THREADS>& threads
    ) {
    m_poll.lock($);
    prv_poll(defer, threads);
    m_poll.unlock($);
    }
    };

    struct state
    {
    std::array<per_thread, CT_THREADS> m_threads;
    poller m_poller = { };
    rl::atomic<per_node*> m_node = { };

    // relacy hooks, so to speak... ;^o
    // relaxed because everything either has not
    // started yet, or everything is already done...
    void
    before()
    {
    per_node* node = new per_node();
    VAR(node->m_data) = 456;
    VAR(node->m_defer) = nullptr;

    m_node.store(node, rl::mo_relaxed, $);
    }


    void
    after()
    {
    per_node* node = m_node.load(rl::mo_relaxed, $);

    if (node)
    {
    delete node;
    }

    // Also clean up any lingering nodes left in the
    poller's m_defer0 at teardown
    per_node* poll_defer = VAR(m_poller.m_defer0);
    while (poll_defer)
    {
    per_node* next = VAR(poll_defer->m_defer);
    delete poll_defer;
    poll_defer = next;
    }
    }

    per_node*
    smr_load(
    unsigned long tidx
    ) {
    for (;;)
    {
    per_node* node0 = m_node.load(rl::mo_relaxed, $);

    if (! node0)
    {
    RL_ASSERT(0); // hould never happen in this test!
    m_threads[tidx].m_haz_0.store(nullptr, rl::mo_relaxed, $);
    break;
    }

    m_threads[tidx].m_haz_0.store(node0,
    rl::mo_relaxed, $);

    // change this to relaxed and it fails...
    per_node* node1 = m_node.load(rl::mo_consume, $);

    if (node0 == node1)
    {
    return node0;
    }
    }

    return nullptr;
    }


    void
    smr_unload(
    unsigned long tidx
    ) {
    // humm...
    m_threads[tidx].m_haz_0.store(nullptr, rl::mo_release, $);
    }
    };

    // the actual test unit...
    struct smr_test_v000
    : rl::test_suite<smr_test_v000, CT_THREADS>
    {
    state m_state = { };

    void before()
    {
    m_state.before();
    }

    void after()
    {
    m_state.after();
    }

    void
    thread_reader(
    unsigned int tidx
    ) {
    for (unsigned long i = 0; i < CT_THREADS_READER_ITER_N;
    ++i)
    {
    per_node* node = m_state.smr_load(tidx);

    if (node)
    {
    // dummy load to poke Relacy... ;^)

    (void)m_state.m_threads[tidx].m_haz_0.load(rl::mo_relaxed, $);

    unsigned long data = VAR(node->m_data);
    RL_ASSERT((data == 123 || data == 456) && (data
    != 0xDEADBEEF));

    m_state.smr_unload(tidx);
    }
    }
    }

    void
    thread_writer(
    unsigned int tidx
    ) {
    per_node* my_defer = nullptr;

    for (unsigned long i = 0; i < CT_THREADS_WRITER_ITER_N;
    ++i)
    {
    per_node* node = new per_node();
    VAR(node->m_data) = 456;
    VAR(node->m_defer) = nullptr;
    per_node* old = m_state.m_node.exchange(node, rl::mo_acq_rel, $);

    if (old)
    {
    VAR(old->m_defer) = my_defer;
    my_defer = old;
    }

    // Opportunistically try to poll and sweep nodes
    m_state.m_poller.try_poll(my_defer, m_state.m_threads);
    }

    // Drain remaining deferred nodes upon thread exit
    m_state.m_poller.poll(my_defer, m_state.m_threads);
    }

    void
    thread(
    unsigned int tidx
    ) {
    if (tidx < CT_THREADS_READERS)
    {
    thread_reader(tidx);
    }

    else
    {
    thread_writer(tidx);
    }
    }
    };
    }
    }

    namespace ct
    {
    namespace smr_test
    {
    namespace test
    {
    inline void
    manifest(
    rl::test_params& p
    ) {
    rl::simulate<ct::smr_test::smr_test_v000>(p);
    }
    }
    }
    }




    ____________________








    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 15 11:26:11 2026
    On 10/13/2026 4:34 PM, Joseph Seigh wrote:
    On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));


    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    It seems like cseq_retry on "every" iteration with its, two?,
    #LoadStore | #LoadLoad barriers is a bit much. Now that I have Relacy
    working fine on my end, and it has all I need for simulating even
    advanced RCU+SMR algos. Well, I will get back into it from time to
    time. Actually, can you get it working? Its a neat way to simulate
    your sync experiments.

    Wait I count an acquire on the item->next.load and then two acquires
    from cseq_retry? Am I missing something critical here Joe?

    It would appear that way.˙˙ That's what quick and dirty means. :)
    If the redundant load/load gets take out then there needs to be
    some other mechanism or documentation to ensure the loads are in
    order.

    Also, cseq_retry on x86/x64 should be native to their TSO anyway? So, no
    ops? But on a SPARC in RMO mode. The damn MEMBAR #LoadStore | #LoadLoad
    would kick in?


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Oct 15 11:49:54 2026
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:
    On 10/11/26 11:11 AM, Joseph Seigh wrote:
    On 9/16/26 6:25 PM, Joseph Seigh wrote:
    https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
    sequential-rcu/

    The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
    somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
    seqlock documentation and it specifically says not to use pointers in
    seqlock protected data and no idea why that is unless the linux kernel
    is doing something weird with memory mapping.

    Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
    that would be a better choice.˙ It might be good for libraries where
    the memory reclamation is hidden from the application.

    * tangentially related to something that I was working on which if far
    more perverse but mostly in a solution that is looking for a problem
    phase so it probably won't go anywhere.

    I threw together a really quick and dirty test to see how much
    overhead all those extra loads would cause.˙ Doesn't appear to
    add any.˙ I will have to add it to the proxies project testcase
    for more extensive testing but it will take a bit of work since
    all the other proxies implement BasicLock and consequential RCU
    readers do not.

    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> & sequence)
    {
    ˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙ }

    } while (cseq_retry(mark, cseq_count));

    Oh shit. This looks like it can be _potential_ "livelock" nation under
    some sustained periods of "heavy enough" load? Can we iterate the whole
    list before many (n) retrys have occurred? Or how many retrys on average
    does it take to iterate a decent sized list from head to tail? Writers churning along in the background...





    As you can see, not a heck of lot of room in that loop
    for the cpu pipeline to deal with the extra load and
    conditional logic.


    Indeed!


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Joseph Seigh@3:633/10 to All on Thu Oct 15 19:26:57 2026
    On 10/15/26 2:49 PM, Chris M. Thomasson wrote:
    On 10/13/2026 10:01 AM, Joseph Seigh wrote:
    On 10/11/26 11:11 AM, Joseph Seigh wrote:


    This is what the code I did a quick and dirty test with looked like

    using csequence_t = uint64_t;

    inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
    sequence)
    {
    ˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
    ˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
    }

    csequence_t mark;

    retry:
    do {

    ˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
    ˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);

    ˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
    ˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
    ˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
    ˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
    ˙˙˙˙ }

    } while (cseq_retry(mark, cseq_count));

    Oh shit. This looks like it can be _potential_ "livelock" nation under
    some sustained periods of "heavy enough" load? Can we iterate the whole
    list before many (n) retrys have occurred? Or how many retrys on average does it take to iterate a decent sized list from head to tail? Writers churning along in the background...


    The list was used to give an idea how much overhead there would be.
    I wouldn't use this for O(n) stuff where n is very large.

    In that this is similar to SEQLOCK, its docs mention the livelock issue.
    The term for these kind of algorithms is obstruction-free (Herlihy).

    You could avoid this with a conventional rwlock. Probably slow you
    down by orders of magnitude.

    You could use RCU since the readers are wait-free. With that many
    updates deferring that much memory, you might OOM.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Oct 16 05:28:07 2026
    Am 14.10.2026 um 23:02 schrieb Chris M. Thomasson:

    It does not need to dec any prior reference before:
    tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
    I must be missing something here. Thanks for your patience.

    You don't understand the code.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)