A note for C programmers

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: A note for C programmers

Post by mar »

syzygy wrote:How to miscompile programs with "benign" data races
The example in 2.1 reads as if it was taken from my code...

Code: Select all

  if (!init_flag) {
    lock();
    if (!init_flag) {
      my_data = ...;
      init_flag = true;
    }
    unlock();
  }
  tmp = my_data;
Interesting, I used this too...
what about

Code: Select all

void (*func)() = init_locked;

void init_locked()
{
    lock()
    if ( func == init_locked )
    {
        // initialize ...

        func = normal;
    }
    unlock()
    normal();
}

void normal()
{
    // ... already initialized, do normal stuff

}
I think that reordering won't hurt in that case, am I wrong?
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: A note for C programmers

Post by syzygy »

mar wrote:what about

Code: Select all

void (*func)() = init_locked;

void init_locked()
{
    lock()
    if ( func == init_locked )
    {
        // initialize ...

        func = normal;
    }
    unlock()
    normal();
}

void normal()
{
    // ... already initialized, do normal stuff

}
I think that reordering won't hurt in that case, am I wrong?
It has precisely the same problem. The assignment "func = normal" may take place before the initialisation is finished, again due to compiler optimisations.

You could lock inside normal(), but of course that it is exactly what you want to avoid.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: A note for C programmers

Post by mar »

syzygy wrote:
mar wrote:what about

Code: Select all

void (*func)() = init_locked;

void init_locked()
{
    lock()
    if ( func == init_locked )
    {
        // initialize ...

        func = normal;
    }
    unlock()
    normal();
}

void normal()
{
    // ... already initialized, do normal stuff

}
I think that reordering won't hurt in that case, am I wrong?
It has precisely the same problem. The assignment "func = normal" may take place before the initialisation is finished, again due to compiler optimisations.

You could lock inside normal(), but of course that it is exactly what you want to avoid.
Yes but it's locked. The problem is that the previous version reads init_flag unlocked (the first read I mean).
Ah sorry you're right. It doesn't matter.
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: A note for C programmers

Post by syzygy »

mar wrote:Yes but it's locked. The problem is that the previous version reads init_flag unlocked (the first read I mean).
Ah sorry you're right. It doesn't matter.
As you probably realised, when calling "func()" your code very similarly reads "func" unlocked.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: A note for C programmers

Post by mar »

syzygy wrote: As you probably realised, when calling "func()" your code very similarly reads "func" unlocked.
Yes. I wonder if volatile func would guarantee that no reordering takes place (on the compiler part). Of course there can be hardware that does the reordering anyway so I guess a memory barrier or something similar in new C standard (volatile atomic maybe) is unavoidable to be 100% portable.
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: A note for C programmers

Post by syzygy »

mar wrote:
syzygy wrote:As you probably realised, when calling "func()" your code very similarly reads "func" unlocked.
Yes. I wonder if volatile func would guarantee that no reordering takes place (on the compiler part). Of course there can be hardware that does the reordering anyway so I guess a memory barrier or something similar in new C standard (volatile atomic maybe) is unavoidable to be 100% portable.
I'm pretty sure volatile will not help, unless you make everything that is initialised volatile.

I think taking and releasing an arbitrary lock after the initialisation and before the assignment to "func" should do the trick, because each of these two operations should act as a memory barrier as well. Maybe nicer than a gcc-specific memory barrier.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: A note for C programmers

Post by mar »

syzygy wrote: I think taking and releasing an arbitrary lock after the initialisation and before the assignment to "func" should do the trick, because each of these two operations should act as a memory barrier as well. Maybe nicer than a gcc-specific memory barrier.
I like this idea. Only hoping the compiler won't be too clever to optimize it away but most probably not as it boils down to external calls.
syzygy
Posts: 5557
Joined: Tue Feb 28, 2012 11:56 pm

Re: A note for C programmers

Post by syzygy »

mar wrote:
syzygy wrote:I think taking and releasing an arbitrary lock after the initialisation and before the assignment to "func" should do the trick, because each of these two operations should act as a memory barrier as well. Maybe nicer than a gcc-specific memory barrier.
I like this idea. Only hoping the compiler won't be too clever to optimize it away but most probably not as it boils down to external calls.
Using pthreads the compiler should not be able (allowed?) to optimise it away. Using C11 I'm less sure.

After reading a bit more about double checked locking I understand that a memory barrier just in front of "init_flag = true" will not suffice if the memory ordering model of the architecture does not preserve the observed order of writes by other threads. The problem is that even though the initialisation is done before "init_flag" is set to "true", another thread might detect that "init_flag" has value "true" but read an uninitialised object. So there must be a memory barrier after the first (unsynchronised) read of "init_flag". This memory barrier must be executed every time, which is unfortunate.

On x86 / x86_64 this problem does not exist and a single (compiler) memory barrier does the trick.

pthread_once() might provide a solution, but for my purposes this option seems awkward to use at best.
Rein Halbersma
Posts: 741
Joined: Tue May 22, 2007 11:13 am

Re: A note for C programmers

Post by Rein Halbersma »

syzygy wrote:
mar wrote:
syzygy wrote:As you probably realised, when calling "func()" your code very similarly reads "func" unlocked.
Yes. I wonder if volatile func would guarantee that no reordering takes place (on the compiler part). Of course there can be hardware that does the reordering anyway so I guess a memory barrier or something similar in new C standard (volatile atomic maybe) is unavoidable to be 100% portable.
I'm pretty sure volatile will not help, unless you make everything that is initialised volatile.

I think taking and releasing an arbitrary lock after the initialisation and before the assignment to "func" should do the trick, because each of these two operations should act as a memory barrier as well. Maybe nicer than a gcc-specific memory barrier.
Double checked locking in C++11 was heavily influenced by a 2004 paper by Alexandrescu and Meyers

http://www.drdobbs.com/cpp/c-and-the-pe ... /184405726
http://www.drdobbs.com/cpp/c-and-the-pe ... /184405772

It discusses the futility of almost every trick that has come up in the last few posts. It should apply equally to aggressively optimizing C compilers as it does to C++.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: A note for C programmers

Post by mar »

Rein Halbersma wrote:Double checked locking in C++11 was heavily influenced by a 2004 paper by Alexandrescu and Meyers

http://www.drdobbs.com/cpp/c-and-the-pe ... /184405726
http://www.drdobbs.com/cpp/c-and-the-pe ... /184405772

It discusses the futility of almost every trick that has come up in the last few posts. It should apply equally to aggressively optimizing C compilers as it does to C++.
In that case, the only reliable way is to explicitly call initialization at program startup while only main thread is running. I hope we can rely at least on that :)
I'm not a big fan of singletons either, they're in fact nothing but wrapped global variables mostly with lazy initialization.
I've seen enough creepy code using singletons already, even though the may seem handy upon first look.