Page 1 of 1

Does anyone have a 64 bit atomic increment decrement?

Posted: Thu Mar 15, 2018 12:05 am
by RichAlgeni
For integers in Windows?

The intrinsic does not seem to work. The SDK kernel32.lib does not seem to export the function, and I cannot find it in kernel32.dll.

There is some assembler listed in other posts, but it does not appear to work.

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Thu Mar 15, 2018 7:07 am
by wilbert

Code: Select all

Procedure.q AtomicIncrement64(*Var64)
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Var64]
    !mov rax, 1
    !lock xadd [rdx], rax
    !add rax, 1
  CompilerElse
    ; store non-volatile registers
    !mov [esp - 4], ebx
    !mov [esp - 8], edi
    ; load 64 bit value into edx:eax
    !mov edi, [p.p_Var64]
    !mov eax, [edi]
    !mov edx, [edi + 4]
    ; 64 bit atomic increment
    !.l:
    !mov ebx, 1
    !mov ecx, 0
    !add ebx, eax
    !adc ecx, edx
    !lock cmpxchg8b [edi]
    !jnz .l
    !mov eax, ebx
    !mov edx, ecx
    ; restore non-volatile registers
    !mov edi, [esp - 8]
    !mov ebx, [esp - 4]
  CompilerEndIf  
  ProcedureReturn  
EndProcedure

Procedure.q AtomicDecrement64(*Var64)
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Var64]
    !mov rax, -1
    !lock xadd [rdx], rax
    !add rax, -1
  CompilerElse
    ; store non-volatile registers
    !mov [esp - 4], ebx
    !mov [esp - 8], edi
    ; load 64 bit value into edx:eax
    !mov edi, [p.p_Var64]
    !mov eax, [edi]
    !mov edx, [edi + 4]
    ; 64 bit atomic decrement
    !.l:
    !mov ebx, -1
    !mov ecx, -1
    !add ebx, eax
    !adc ecx, edx
    !lock cmpxchg8b [edi]
    !jnz .l
    !mov eax, ebx
    !mov edx, ecx    
    ; restore non-volatile registers
    !mov edi, [esp - 8]
    !mov ebx, [esp - 4]
  CompilerEndIf
  ProcedureReturn   
EndProcedure


A.q = 5
For i = 1 To 10
  Debug AtomicIncrement64(@A)
Next
It's also possible to use one Add procedure and use -1 for decrement.

Code: Select all

Procedure.q AtomicAdd64(*Var64, Value64.q)
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Var64]
    !mov rax, [p.v_Value64]
    !lock xadd [rdx], rax
    !add rax, [p.v_Value64]
  CompilerElse
    ; store non-volatile registers
    !mov [esp - 4], ebx
    !mov [esp - 8], edi
    ; load 64 bit value into edx:eax
    !mov edi, [p.p_Var64]
    !mov eax, [edi]
    !mov edx, [edi + 4]
    ; 64 bit atomic add
    !.l:
    !mov ebx, [p.v_Value64]
    !mov ecx, [p.v_Value64 + 4]
    !add ebx, eax
    !adc ecx, edx
    !lock cmpxchg8b [edi]
    !jnz .l
    !mov eax, ebx
    !mov edx, ecx
    ; restore non-volatile registers
    !mov edi, [esp - 8]
    !mov ebx, [esp - 4]
  CompilerEndIf  
  ProcedureReturn  
EndProcedure


A.q = 5

For i = 1 To 10
  Debug AtomicAdd64(@A, -1)
Next

For i = 1 To 6
  Debug AtomicAdd64(@A, 1)
Next

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Thu Mar 15, 2018 11:20 pm
by RichAlgeni
Always appreciate your assistance Wilbert!

You're work is most instructive and helpful!

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Fri Mar 16, 2018 2:20 am
by RichAlgeni
I'm sorry I have to ask this, but...

Could you write an Assembler process to force the variable (quad integer pointer) back to 0?

I am getting error C0000264, STATUS_RESOURCE_NOT_OWNED arbitrarily in this code:

Code: Select all

    While Not TryAcquireSRWLockExclusive(*updateLock)
        Delay(20)
    Wend
    
    AddElement(updateMessageLog())
    updateMessageLog()\messageLogKey = *messageData\messageKey
    updateMessageLog()\messageLogTyp = #snpp
    updateMessageLog()\messageResult = result

    ReleaseSRWLockExclusive(*updateLock)
Which seems impossible as the pointer used in the lock is not accessed in any other part of the program.

My thought is that I could use it as my own lock process.

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Fri Mar 16, 2018 6:43 am
by wilbert
RichAlgeni wrote:Could you write an Assembler process to force the variable (quad integer pointer) back to 0?
I'm not exactly sure if I understand what you want. :?
Do you want a procedure to set the value like the code below or do you need something else ?

Code: Select all

Procedure.q AtomicSetValue64(*Var64, Value64.q)
  ; the return value is the old value of *Var64
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Var64]
    !mov rax, [p.v_Value64]
    !lock xchg [rdx], rax
  CompilerElse
    ; store non-volatile registers
    !mov [esp - 4], ebx
    !mov [esp - 8], edi
    ; load current value into edx:eax
    !mov edi, [p.p_Var64]
    !mov eax, [edi]
    !mov edx, [edi + 4]
    ; 64 bit atomic exchange
    !.l:
    !mov ebx, [p.v_Value64]
    !mov ecx, [p.v_Value64 + 4]
    !lock cmpxchg8b [edi]
    !jnz .l
    ; restore non-volatile registers
    !mov edi, [esp - 8]
    !mov ebx, [esp - 4]
  CompilerEndIf  
  ProcedureReturn  
EndProcedure

Procedure.q AtomicAdd64(*Var64, Value64.q)
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Var64]
    !mov rax, [p.v_Value64]
    !lock xadd [rdx], rax
    !add rax, [p.v_Value64]
  CompilerElse
    ; store non-volatile registers
    !mov [esp - 4], ebx
    !mov [esp - 8], edi
    ; load current value into edx:eax
    !mov edi, [p.p_Var64]
    !mov eax, [edi]
    !mov edx, [edi + 4]
    ; 64 bit atomic add
    !.l:
    !mov ebx, [p.v_Value64]
    !mov ecx, [p.v_Value64 + 4]
    !add ebx, eax
    !adc ecx, edx
    !lock cmpxchg8b [edi]
    !jnz .l
    !mov eax, ebx
    !mov edx, ecx
    ; restore non-volatile registers
    !mov edi, [esp - 8]
    !mov ebx, [esp - 4]
  CompilerEndIf  
  ProcedureReturn  
EndProcedure


A.q = 5

For i = 1 To 10
  Debug AtomicAdd64(@A, 1)
Next

AtomicSetValue64(@A, 0)

For i = 1 To 10
  Debug AtomicAdd64(@A, -1)
Next

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Fri Mar 16, 2018 3:23 pm
by RichAlgeni
Let me explain my thought process, and see if it makes sense to you.

1. The initial value of the Global integer will be 0.

2. Threads created will use AtomicIncrement64() to increment the value, when work is required.

3. Only when the integer returned to a thread is 1 will the thread be considered to be granted access to a locked resource.

4. Any other value returned (2, 3, etc.) and the thread will wait a period of time, then proceed to step 2.

5. The thread that does the work will use AtomicSetValue64() to set the value of the integer back to 0 when the work is complete, indicating that the resource is unlocked and available to another thread to process work needed on the resource.

6. The thread that did the work will proceed to step 2.

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Fri Mar 16, 2018 4:45 pm
by wilbert
Sounds logical :)

You could also consider a toggle bit; not sure what would work best.

Code: Select all

Procedure AtomicBitSet(*Mem, BitIdx.a = 0)
  ; the return value is the old bit value
  !movzx eax, byte [p.v_BitIdx]
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Mem]
    !lock bts [rdx], eax
  CompilerElse
    !mov edx, [p.p_Mem]
    !lock bts [edx], eax
  CompilerEndIf
  !setc al
  ProcedureReturn  
EndProcedure

Procedure AtomicBitReset(*Mem, BitIdx.a = 0)
  ; the return value is the old bit value
  !movzx eax, byte [p.v_BitIdx]
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [p.p_Mem]
    !lock btr [rdx], eax
  CompilerElse
    !mov edx, [p.p_Mem]
    !lock btr [edx], eax
  CompilerEndIf  
  !setc al
  ProcedureReturn  
EndProcedure



Global Flags = 0

Procedure MyThreadProc(Value)
  Repeat
    While AtomicBitSet(@Flags)
      Delay(20)
    Wend
    For i = 1 To 10
      Debug "Thread "+Str(Value)+" : "+Str(i)
      Delay(100)  
    Next
    AtomicBitReset(@Flags)
    Delay(100)  
  ForEver  
EndProcedure

CreateThread(@MyThreadProc(), 1)
CreateThread(@MyThreadProc(), 2)

Delay(10000)
AtomicBitSet sets bit 0 of the Flags value always to 1.
If it previously was 0, you can proceed. If not, you can consider it locked.
At the end of the procedure you can reset the bit to indicate it is no longer locked.

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Fri Mar 16, 2018 10:50 pm
by RichAlgeni
Ok, so we check the returned value, to see if the bit was changed by AtomicBitSet().

If it was, we are free to proceed with the locked resource.

Brilliant! Thanks so much Wilbert!!!

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Sat Mar 17, 2018 6:54 am
by wilbert
RichAlgeni wrote:Ok, so we check the returned value, to see if the bit was changed by AtomicBitSet().

If it was, we are free to proceed with the locked resource.
Yes, if it previously was 0, it has changed from 0 to 1 so a lock was acquired. :)
If you need to protect different resources, you can use a different bit index value.
With a quad value, you can protect 64 different resources.
The procedure itself supports a bit index from 0 - 255. If you want to use bit index 64 - 255, you can do it like this.

Code: Select all

Global *LockBits = AllocateMemory(32); allocate 256 bits

AtomicBitSet(*LockBits, 200)
AtomicBitReset(*LockBits, 200)

Re: Does anyone have a 64 bit atomic increment decrement?

Posted: Sat Mar 17, 2018 8:49 pm
by RichAlgeni
Brilliant! Thanks so much Wil!!!

I have 9 Windows Services programs that I am going to replace Slim Reader Writer with this.

I had high hopes for SRW. I don't understand how it could randomly fail like that.

So be it!

Thanks again!

Rich