On Sunday 26 August 2012 12:53:41 Marc Mutz wrote:
> On Sunday August 26 2012, David Faure wrote:
> > On Sunday 26 August 2012 11:28:06 Julian Seward wrote:
> > > On Sunday, August 26, 2012, David Faure wrote:
> > > > Thiago expects that helgrind can't autodetect this case and that
> > > > helgrind- macros markup is needed in Qt, I'm fine with adding that if
> > > > you guys agree -- after you show me how by modifying the attached
> > > > example :-)
> > > 
> > > IIUC then, QBasicAtomicInt::{loadAcquire,storeRelease} just does normal
> > > loads and stores of an int.  Right?
> > 
> > Yep (on x86). This whole API exists so that other architectures can get
> > another implementation.
> > 
> > > What atomicity properties are you expecting onDemandNumber() to have?
> > > Viz, how is onDemandNumber supposed to behave when you have multiple
> > > threads doing it at the same time on the same location?
> > 
> > static int onDemandNumber() {
> > 
> >     static QBasicAtomicInt sharedInt = { 0 };
> >     if (!sharedInt.loadAcquire()) {
> >     
> >         // some calculation goes here
> >         sharedInt.storeRelease(42);
> >     
> >     }
> >     return sharedInt.loadAcquire();
> > 
> > }
> 
> If sharedInt is a std::atomic<int>, then the above is not a C++11 data race,

No, it's a bare int, just like Qt5's QBasicAtomicInt does by default on x86.
You missed the initial email with the testcase, here it is 
(testcase_atomic_ops_helgrind.cpp).

> so Helgrind shouldn't warn.

Helgrind warns, hence my mail here.

> If 'some calculation goes here' doesn't write
> to memory global memory, you could even drop the memory ordering. Atomics
> don't participate in data races, but they might also not establish the
> happens-before that you need elsewhere.

This is obviously a reduced testcase ;)
The calculation, in the example I took, is to register the metatype, which is 
global stuff, but which is somewhat safe if done twice (it will return the same 
number). Inside QMetaType::registerNormalizedType there's a mutex. The whole 
point, though, as I see it, is to avoid locking in the very common case where 
the metatype has been registered already.

> > I think the point is that the first call to loadAcquire() should either
> > return 0 or 42, but never some intermediate value due to a write in
> > progress.
> 
> Correct. What's more: for any thread, any read of an atomic variable must
> return a value previously read, or a value written later by another thread.
> IOW: once a thread sees 42, it can't magically see 0 the next time.
> According to Hans Boehm, this may happen on IA-64 because the architecture
> allows to reorder reads from the same memory location.

Which is why the atomic stuff expands to different code on IA-64, so no problem.

> If it writes to a shared memory location, the code contains a C++11 data
> race, and you need more complex synchronisation

Yes, which is there, but that's not the point. From what you say, the code is 
fine, so helgrind shouldn't warn about sharedInt itself. However, Julian says 
the code is not fine, the compiler could generate code that doesn't work 
correctly. I don't know who to trust at this point, I'm caught in the middle 
:-).

If this is safe, helgrind should be fixed. If this is not safe, Qt should be 
fixed. But it sounds to me like different people have different interpretations 
on what is safe, in such code :-)

I just realized one thing though: I was wrong when I said "it is racy because 
the value could be 0 or 42", that is not a race. Writing the same code with a 
mutex around the int leads to exactly this behavior (0 or 42), which is fine. 
See testcase_int_mutex_helgrind.cpp. No helgrind warning, and I think everyone 
agrees that this code is ok. So if ints are read and written atomically, then 
the helgrind warning in the initial testcase is wrong? (ok I guess it's more 
complicated, if the compiler can reorder stuff in the first testcase and not 
the 
second....).

-- 
David Faure, fa...@kde.org, http://www.davidfaure.fr
Sponsored by Nokia to work on KDE, incl. KDE Frameworks 5
#include <pthread.h>

template <typename T> struct QAtomicOps // qgenericatomic.h
{
    static inline
    T loadAcquire(const T &_q_value)
    {
        T tmp = *static_cast<const volatile T *>(&_q_value);
        return tmp;
    }

    static inline
    void storeRelease(T &_q_value, T newValue)
    {
        *static_cast<volatile T *>(&_q_value) = newValue;
    }
};

class QBasicAtomicInt // qbasicatomic.h
{
public:
    typedef QAtomicOps<int> Ops;
    int _q_value;

    int loadAcquire() const { return Ops::loadAcquire(_q_value); }
    void storeRelease(int newValue) { Ops::storeRelease(_q_value, newValue); }
};

// Modelled after qt_metatype_id()
static int onDemandNumber() {
    static QBasicAtomicInt sharedInt = { 0 };
    if (!sharedInt.loadAcquire()) {
        // some calculation goes here
        sharedInt.storeRelease(42);
    }
    return sharedInt.loadAcquire();
}

void * threadStart(void *)
{
    onDemandNumber();
    onDemandNumber();
    onDemandNumber();
    onDemandNumber();
    return 0;
}


int main( int argc, char** argv ) {
    pthread_t thread1;
    if ( pthread_create(&thread1, 0, threadStart, 0) )
        return 1;
    pthread_t thread2;
    if ( pthread_create(&thread2, 0, threadStart, 0) )
        return 1;

    void* v;
    pthread_join(thread1, &v);
    pthread_join(thread2, &v);
    return 0;
}



#include <pthread.h>
#include <QtCore/QMutex>

class MyBasicAtomicInt
{
public:
    MyBasicAtomicInt() : _q_value(0) {}
    int _q_value;
    mutable QMutex mutex;

    int loadAcquire() const { QMutexLocker lock(&mutex); return _q_value; }
    void storeRelease(int newValue) { QMutexLocker lock(&mutex); _q_value = newValue; }
};

// Modelled after qt_metatype_id()
static int onDemandNumber() {
    static MyBasicAtomicInt sharedInt;
    if (!sharedInt.loadAcquire()) {
        // some calculation goes here
        sharedInt.storeRelease(42);
    }
    return sharedInt.loadAcquire();
}

void * threadStart(void *)
{
    onDemandNumber();
    onDemandNumber();
    onDemandNumber();
    onDemandNumber();
    return 0;
}


int main( int argc, char** argv ) {
    pthread_t thread1;
    if ( pthread_create(&thread1, 0, threadStart, 0) )
        return 1;
    pthread_t thread2;
    if ( pthread_create(&thread2, 0, threadStart, 0) )
        return 1;

    void* v;
    pthread_join(thread1, &v);
    pthread_join(thread2, &v);
    return 0;
}




------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/
_______________________________________________
Valgrind-users mailing list
Valgrind-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/valgrind-users

Reply via email to