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.



Reply via email to