Paul C
Paul C

Reputation: 8457

Can I use cmpxchg16b to atomically copy a pointer to a pointer and an int, while simultaneously incrementing the int (atomic reference counting)?

My goal is to copy a pointer and increment a reference counter, atomically, without locking. Can I use cmpxchg16b, or maybe there is a simpler way? I'm pretty clueless with assembler, so pardon any glaring errors.

edit: Something similar to this, but for this intel architecture. (I'm still trying to see if I can adapt what is in the dr. dobbs article to my platform. Atomic ops seem to be done a bit differently)

Example memory

struct MyThing {
  void * pSomeObj;
  uint64_t counter;
__attribute__((aligned(16)));
};

What I want to do is something like this.

MyThing* globalSource = ...;

...
MyThing* dest;
//copy the pointer while incrementing counter all in one shot. (to prevent data race where a copy is done and some other thread deletes the pointer before the the counter gets a chance to be updated)
AtomicCopyAndIncrement(dest, globalSource);

Logically, this is what I want to accomplish:

bool AtomicCopyAndIncrement(MyThing *dest, MyThing *src) {
    //begin atomic
    dest = src;
    dest->counter++;
    //end atomic
    return true;
}

This function just swaps everything as-is. I think it is right.

bool AtomicSwap(MyThing *dest, MyThing *src)
{
    bool swap_result;
    __asm__ __volatile__
    (
     "lock cmpxchg16b %0;"  
     "setz       %3;"  

     : "+m" (*dest), "+a" (dest->pSomeObj), "+d" (dest->counter), "=q" (swap_result)
     : "b" (src->pSomeObj), "c" (src->counter)
     : "cc", "memory"
     );
    return swap_result;
}

This function should take src and set it equal dest while incrementing the counter that gets assigned in dest.

bool AtomicCopyAndIncrement(MyThing *dest, MyThing *src)
{
    bool swap_result;
    __asm__ __volatile__
    (
     "lock cmpxchg16b %0;"
     "setz       %3;"
     //all wrong
     : "+m" (*dest), "+a" (dest->pSomeObj), "+d" (dest->counter), "=q" (swap_result)
     : "b" (src->pSomeObj), "c" (src->counter +1)
     : "cc", "memory"
     );
    return swap_result;
}

This decrements the counter, but copies the point back to itself. I don't know if this is necessary, but I think it is right. edit I'm pretty sure it is wrong.

bool AtomicDecrement(MyThing *dest)
{
    bool swap_result;
    __asm__ __volatile__
    (
     "lock cmpxchg16b %0;"  
     "setz       %3;"  

     : "+m" (*dest), "+a" (dest->pSomeObj), "+d" (dest->counter), "=q" (swap_result)
     : "b" (dest->pSomeObj), "c" (dest->counter -1)
     : "cc", "memory"
     );
    return swap_result;
}

Usage scenario would be like this:

Thread 0:

MyThing* newThing;
do {
  if (newThing) delete newThing;
  newThing = new MyThing;
  newThing->pSomeObj->SomeUpdate();
}
while (AtomicSwap(newThing, globalThing));
//then deal with the memory in newThing

Threads 1-N:

MyThing *currentThing = new MyThing;
//make currentThing->pSomeObj the same as globalThing->pSomeObj, and increment the counter
AtomicCopyAndIncrement(currentThing, globalThing);

Upvotes: 3

Views: 465

Answers (1)

gexicide
gexicide

Reputation: 40058

Consider using std::atomic. It will compile to a 128bit CAS if the target architecture offers it and is much cleaner, easier to use, and more platform independent than inline assembler.

Here is an example how you can use it to update the pointer and increment the counter atomically:

std::atomic<MyThing>* target = ...;
MyThing oldValue = target->load();
MyThing newValue { newPointer, 0 };
do{
    newValue.counter = oldValue.counter+1;
while(target->compare_exchange_strong(oldValue,newValue))

Upvotes: 3

Related Questions