This is the mail archive of the
gcc@gcc.gnu.org
mailing list for the GCC project.
Re: Heads-up: volatile and C++
- From: Melissa O'Neill <oneill at cs dot hmc dot edu>
- To: Jason Merrill <jason at redhat dot com>
- Cc: gcc at gcc dot gnu dot org
- Date: Sun, 24 Apr 2005 15:07:05 -0700
- Subject: Re: Heads-up: volatile and C++
I'm in the camp that believes that the committee shouldn't change the
semantics of volatile. There are three key reasons to my argument:
- "You don't pay for what you don't use"
- Nonthreaded uses of volatile
- Avoiding insidious change
You don't pay for what you don't use
------------------------------------
However high the benefits might be to calling isync/sync/eieio etc.,
there will always be situations where they are not necessary. In the
same way that C++ allows you not to use virtual function dispatch,
and not to use garbage collection, even though both are often useful
timesavers and avoid a fair amount of headscratching about code
behavior, you should be wary of saddling programmers with additional
costs in the interests of more predicable code behavior for parallel
programs. It might be cumbersome to add a new keyword, such as
"ordered" or some standardized builtins for barriers, but doing so
would be more within the spirit of C++.
As has already been mentioned in the discussion, there exist parallel
algorithms that don't need strong memory ordering. It is wrong to
penalize those algorithms.
Moreover, some code that uses volatile isn't threaded at all -- it
certainly shouldn't pay for thread synchronization instructions that
it doesn't need.
Nonthreaded uses of volatile
-----------------------------
Here is a canonical example of single-threaded code that relies on
the current semantics of volatile and does not need any additional
synchronization:
#include <stdio.h>
#include <signal.h>
volatile int interrupted = 0;
void interrupt(int ignored)
{
interrupted = 1;
}
int main()
{
int i;
signal(SIGINT, interrupt);
for(i = 0; i < 10000000; ++i) {
// Do some more of a complicated calculation
// ... calculation code ...
if (interrupted)
break;
}
printf("i = %d\n", i);
}
Importantly, assuming the complex calculation doesn't involve any
(noninlined) function calls, a good compiler could push the read of
the volatile variable up in the calculation code such that memory
latency might not cost anything. Imagine what throwing in an isync
that no one asked for might do for the pipelining of this loop.
It's also possible to imagine other, wackier, examples, such as the
one shown below, that uses memory-mapping tricks to create an unusual
memory layout where the same physical memory lies at multiple virtual
addresses -- a situation no sane compiler would expect, but can be
taught to cope with using volatile.
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
int main()
{
int fd = open("/tmp/scratch", O_CREAT|O_TRUNC|O_RDWR, 0666);
assert(fd >= 0);
lseek(fd, 4095, SEEK_SET);
write(fd, "", 1);
void* mem1 = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, 0);
void* mem2 = mmap(mem1+4096, 4096, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_FIXED, fd, 0);
assert(mem2 == mem1 + 4096);
printf("%p %p\n", mem1, mem2);
volatile char* weird = mem1;
weird[0] = 'A';
weird[4096] = 'B';
printf("%c %c\n", weird[0], weird[4096]);
}
(which should print B B, but will print A B when optimized without
volatile)
I'm sure I'm just scratching the surface here. It wouldn't surprise
me if some programmers have used volatile to handle issues with
strict aliasing, for example.
Avoiding Insidious Change
-------------------------
It took many years to have compilers on the market that had good
support for the current C++ standard and, even today, people continue
to use significantly noncompliant compilers. That pattern is likely
to be repeated with the next C++ standard. Imagine the world with
two sets of compilers, those that support "the old volatile
semantics" and those that support "the new volatile semantics". The
worst-case scenario is people compiling code written for "new
volatile" compilers on "old volatile" compilers and seeing no error
messages and very, very subtle failure modes. A marginally better
case is code written for "old volatile" compilers that performs
redundant synchronization on "new volatile" compilers.
Change has happened many times before, but it's usually different in
that it's very obvious when you're trying to use a "new standard"
feature that isn't supported. Typically your code won't even compile.
There is also the question of whether the next C standard would adopt
a revised meaning for volatile. If not, the committee will have
created more confusion for programmers who need to live in both worlds.
In conclusion, I hope you will argue strongly against this change.
The core of the idea may be a good one -- what is wrong is changing
an existing, fairly well-understood concept.
Melissa.