[comp.lang.c] The world is not ready for 'volatile'

stuart@bms-at.UUCP (Stuart Gathman) (12/24/88)

With all this talk about 'volatile' being necessary for good optimization,
I wonder why compilers don't do the optimizations already available without
it?  Examples,

	mov.l	d5,d0
	mov.l	d0,d4			; typical "optimized" code !
	mov.l	&0,d0

	or

	mov.w	&1234,d0
	ext.l	d0
	sub.l	d2,d0			; "optimized" code . . .
	mov.w	d0,&5678

	or (for Intel fans)

	register char far *p;
	...
	p->a = 1;
	p->b = 2;

	becomes (on MSC & Turbo C)

	les	bx,[bp-4]
	mov	byte ptr es:[bx],1
	les	bx,[bp-4]		; "optimized" code !
	mov	byte ptr es:[bx+1],2

I could go on and on.  I have used Aztec, Turbo, MS, Motorola,
GCC (better than most), Convergent Technologies (the worst), 
and many other compilers.

When compilers don't take advantage of existing optimizations,
what's the big deal about "volatile"?

Furthermore, all "auto" and "register" variables are subject
to the same optimizations as (absence of) "volatile" gives you if their
address is never taken (and "noalias" to boot).  If you optimize
"static" data as well, only "extern" data gets volatile treatment.  
In my programs, this is precisely the data requiring the "volatile" keyword!
(And device drivers would make all magic addresses "extern".)

If compilers would simply use the optimization opportunities already
available, the improvement with "volatile" would be marginal.

P.S.  don't post counter examples like:

	int a,b, *p = &a;
	p[1] = 2;

      this is not portable 'C' (or even good programming).
-- 
Stuart D. Gathman	<stuart@bms-at.uucp>
			<..!{vrdxhq|daitc}!bms-at!stuart>

bill@twwells.uucp (T. William Wells) (12/25/88)

In article <141@bms-at.UUCP> stuart@bms-at.UUCP (Stuart Gathman) writes:
: I could go on and on.  I have used Aztec, Turbo, MS, Motorola,
: GCC (better than most), Convergent Technologies (the worst),
: and many other compilers.
:
: When compilers don't take advantage of existing optimizations,
: what's the big deal about "volatile"?

While many compilers suck wind, many others are very good.  Also,
remember that some optimizations are easier than others.  The
examples you gave were failures in the peephole optimizer; most such
do their thing by handling special cases. What you found were special
cases not handled by particular peephole optimizers.  Tightening up
the peephole optimizer, past a certain point, is a waste of time: all
additional optimizations will be marginal.

If I had my 'druthers, I'd rather take advantage of volatile instead
of trying to tighten up a peephole optimizer. Why?  Because the
absence of volatile permits me to make wholesale optimizations.  In
other words, if I assume that all variables are implicitly volatile,
I must not assume that the variable retains its values between
accesses; nor may I assume that a write to a variable which can be
determined to be unnecessary (for the algorithm) can be deleted. This
means that all memory references implied by the code must remain
there; this prohibits some of the most effective optimizations from
being done.

Another advantage of this kind of optimization is that it is machine
independent. Many optimizers have two parts, one for machine
independent optimizations and one for machine dependent optimizations.
Compilers that have such often can have the code generators changed,
resulting in a compiler that generates code for a different machine.
Optimizations done that are machine independent will then apply to
all versions of the compiler, not just one.

: Furthermore, all "auto" and "register" variables are subject
: to the same optimizations as (absence of) "volatile" gives you if their
: address is never taken (and "noalias" to boot).  If you optimize
: "static" data as well, only "extern" data gets volatile treatment.

Not true. Consider that a static variable might be accessed from a
function called by a signal handler. In other words, in

	static int Var;

	int
	foo()
	{
		++Var;
	}

Since foo() can be called from a signal handler in another translation
unit, Var is also "volatile".

: In my programs, this is precisely the data requiring the "volatile" keyword!
: (And device drivers would make all magic addresses "extern".)

But you're ignoring an important point: volatile does not only apply
to variables, it also applies to, e.g., things pointed to.  If I were
writing a device driver to handle several devices, I might well
declare a structure containing volatile elements and pass the address
of the structure to some control routine. Or a pointer might well
point to a variable which is changed in a signal handler.

Because of this, any pointed to variable, absent the possibility of
using the volatile keyword, must be considered volatile.

: If compilers would simply use the optimization opportunities already
: available, the improvement with "volatile" would be marginal.

On what evidence do you base that?

---
Bill
{uunet|novavax}!proxftl!twwells!bill

chris@mimsy.UUCP (Chris Torek) (12/26/88)

In article <275@twwells.uucp> bill@twwells.uucp (T. William Wells) writes:
>... Because of this, any pointed to variable, absent the possibility of
>using the volatile keyword, must be considered volatile.

Or, you can put up with a long, slow, tortuous (and perhaps torturous)
inter-module analysis phase that picks up those cases that it can, and
assumes the worst (volatility) for those it cannot.  We do not know how
to do this well (nor fast) now, but I predict that in a number of
years, we will---just as compilers are (only just now) starting to do
decent register allocation.

Incidentally, Mr. Grandi% has a point (which I might note that I myself
made not too long ago) about `register':  It implies not-volatile and
not-aliased; and it does so in a way that the compiler can reasonably
enforce.  It is conceivable that one could make use of the existing
keyword (and overload C keywords yet further) for optimisations it does
not now allow.  [Please note that I make no judgement as to
tastefulness, desirability, etc.!  I got into this last time because
someone mistook a hypothetical argument for a real one.]
-----
% I do hope I spelled your last name right.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gwyn@smoke.BRL.MIL (Doug Gwyn ) (12/27/88)

In article <15166@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>... ["register"] implies not-volatile and
>not-aliased; and it does so in a way that the compiler can reasonably
>enforce.

No, I reject this claim.  A STORAGE CLASS by no means obviates the
need for type qualifiers.  You could conceivably invent a language
with only type qualifiers (one of which you may choose to name
"register") and no storage classes, but it wouldn't be reasonable
to call that language "C".

chris@mimsy.UUCP (Chris Torek) (12/29/88)

>In article <15166@mimsy.UUCP> I suggested that
>>... ["register"] implies not-volatile and not-aliased; and it does so
>>in a way that the compiler can reasonably enforce.

In article <9236@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>No, I reject this claim.

You mean to say that a register could be aliased?  Not in C!  How
about a volatile register?  It is a possibility, but it seems entirely
unnecessary.

>A STORAGE CLASS by no means obviates the need for type qualifiers.

I did not say that.  I said `register implies not-volatile, not-
aliased': that `volatile register' and `aliased register' are
nonsensical combinations.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gwyn@smoke.BRL.MIL (Doug Gwyn ) (01/02/89)

>[I wrote:]
>>A STORAGE CLASS by no means obviates the need for type qualifiers.

In article <15171@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>I did not say that.  I said `register implies not-volatile, not-
>aliased': that `volatile register' and `aliased register' are
>nonsensical combinations.

Of course there are nonsensical expressions in ANY version of C
that are not ruled "illegal" just because they're useless.
But I thought you were trying to claim that "volatile" could have
been dispensed with and the necessary additional semantics
invested in "register", and THAT is what I dispute, because type
qualifiers can apply at any of the "levels" of a type
declaration, unlike storage class specifiers (which apply to the
identifier, in effect).  Thus
	register const int * volatile * const ptr = SOMETHING;
has a well-defined and possibly useful meaning, which would be
impossible (I think) to mimic correctly by stretching "register"
to serve as a type qualifier while not breaking existing usage
of "register".  In any case the switch from a storage class
specifier to a type qualifier would be pretty drastic!

geoff@endor.harvard.edu (Geoff Clemm) (01/03/89)

In article <15171@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>>In article <15166@mimsy.UUCP> I suggested that
>>>... ["register"] implies not-volatile and not-aliased; and it does so
>>>in a way that the compiler can reasonably enforce.
>
>In article <9236@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>>No, I reject this claim.

I would have to second this rejection.  There is nothing in the
reference manual that lets you count on register variables not being
volatile.

>You mean to say that a register could be aliased?  Not in C!  How
>about a volatile register?  It is a possibility, but it seems entirely
>unnecessary.

Now Chris, this is a total waffle.  For folks that are seriously concerned
about portability, one needs a stronger argument than that "it seems entirely
unnecessary" for a compiler writer to do something -- especially since many
compiler writers ignore register declarations, which means register variables
will be as volatile or non-volatile as any other variable.

Geoff Clemm

chris@mimsy.UUCP (Chris Torek) (01/04/89)

>>>In article <15166@mimsy.UUCP> I suggested that
>>>>... ["register"] implies not-volatile and not-aliased; and it does so
>>>>in a way that the compiler can reasonably enforce.
(by the way, the word `implies' is key.)

>>In article <9236@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>>>No, I reject this claim.

>In article <15171@mimsy.UUCP> I answered with:
>>You mean to say that a register could be aliased?  Not in C!  How
>>about a volatile register?  It is a possibility, but it seems entirely
>>unnecessary.

In article <903@husc6.harvard.edu> geoff@endor.harvard.edu
(Geoff Clemm) replies:
>I would have to second this rejection.  There is nothing in the
>reference manual that lets you count on register variables not being
>volatile.

You misinterpret (everyone always misinterprets! :-) ).

The dpANS allows you to write

	register const k = 4;
and
	register volatile v;

but, I ask, what do these mean?  The answer is that they mean the same
thing as

	const k = 4;
and
	volatile v;

except that you may not take the address of either variable---which
therefore means that the compiler need not even assign an address to
either variable.  This makes the effect (if any) of volatile on a
register declaration dubious at best.

>Now Chris, this is a total waffle.  For folks that are seriously concerned
>about portability, one needs a stronger argument than that "it seems entirely
>unnecessary" for a compiler writer to do something -- especially since many
>compiler writers ignore register declarations, which means register variables
>will be as volatile or non-volatile as any other variable.

For folks concerned with portability, the word `volatile' does not
exist in the language (with one exception), since the actual effect of
the volatile qualifier is compiler-dependent.  The exception is in
functions using setjmp.  Optimising such functions well is hard, and
the dpANS simply grants license to ignore the effect of longjmp on
program flow during optimisation.  This was, in my opinion, the wrong
thing to do; but while we are stuck with it, remember that I was not
saying that `volatile' and `register' are opposites, but merely that
`register' *ought* (in some more perfect world where the dpANS were
different) to imply not-aliased (it does) and not-volatile (it does
not).  Actually, again in my opinion, in a still more perfect world,
both the dpANS and `old C' would not have the `register' keyword
anyway.

Moreover, for those again concerned with portability, it is necessary
never to use `register volatile' even in functions using setjmp, since
in old compilers---where one would `#define volatile /*empty*/'---
registers are the *only* non-volatile variables.  In particular, the
PDP-11 V7 C runtime system, the 3.0, 4.0, and 4.1BSD runtime systems,
and most if not all SunOS runtime systems, effectively restore register
variables to their state at the time of the *setjmp* call:

	f()
	{
		register int r = 1;
		jmp_buf j;

		if (setjmp(j)) {
			(void) printf("r is now %d\n", r);
			return;
		}
		r = 2;
		longjmp(j, 1);
		/* NOTREACHED */
	}

This program prints `1', not `2', in a number of systems.  This is
precisely the effect produced by an optimising compiler given a
non-volatile variable `r' and ignoring the implicit `goto' produced by
the setjmp/longjmp pair.  The variable life analysis goes like this:

	register int r = 1	| r = constant 1
	jmp_buf j		| r = constant 1, j = constant <uninit>

	if (setjmp(j))		| r = constant 1, j = <unknown 1>
		/* (remember, j is passed by address) */
	printf(...)		| r = constant 1, j = <unknown 1>
	return	
	r = 2			| r = constant 2, j = <unknown 1>
	longjmp(j, 1)		| r = constant 2, j = <unknown 2>
		/* note that <unknown 1> != <unknown 2> */
	implicit return		| r = dead, j = dead

At this point, the compiler should replace known constants (the one
instance of r) with their values.  It can then delete both writes (r=1
and r=2) since they are to dead variables.  The optimised function is
thus

	if (setjmp(j)) {
		(void) printf("r is now %d\n", 1);
		return;
	}
	longjmp(j, 1);

We can merrily write `register volatile int r = 1', and a dpANS-
conformant compiler will do the `right thing', but the program is
not portable to some widespread versions of `old C' (including SunOS
3.2, where I just tested it).  The important thing is not that these
implementations are not dpANS conformant (they are not), but rather
that `volatile int r = 1' *will* work.

Then too, I doubt that `register volatile v' will do anything different
from `volatile v' in any worthwhile compiler.  (Note key words `I
doubt': this is an opinion, not a fact.)

Anyway, the point is that `register volatile v' (but not `register
volatile *v') is effectively useless.  It is therefore possible---but
not necessarily wise---to make `register' mean `not volatile', and to
declare the volatile qualifier and the register storage class
incompatible.  I do not claim that this encompasses all uses for
`volatile'.  I claim only what I said: register implies not-aliased,
and does so in a way the compiler can enforce (`&v' not allowed); and
register implies (through effective uselessness) not-volatile, again in
a way that the compiler can enforce (since volatility is purely the
compiler's notion anyway, as far as code generation is concerned).
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gwyn@smoke.BRL.MIL (Doug Gwyn ) (01/04/89)

In article <15248@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>For folks concerned with portability, the word `volatile' does not
>exist in the language (with one exception), since the actual effect of
>the volatile qualifier is compiler-dependent.

No, that's wrong.  A conforming compiler is obliged to generate code
for "volatile" object accesses strictly in accordance with the
virtual machine model (no caching).  It is OTHER mechanisms for
accessing a volatile object during execution that are not specified.

chris@mimsy.UUCP (Chris Torek) (01/04/89)

>In article <15248@mimsy.UUCP> I claimed that
>>For folks concerned with portability, the word `volatile' does not
>>exist in the language (with one exception), since the actual effect of
>>the volatile qualifier is compiler-dependent.

In article <9273@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>No, that's wrong.  A conforming compiler is obliged to generate code
>for "volatile" object accesses strictly in accordance with the
>virtual machine model (no caching).  It is OTHER mechanisms for
>accessing a volatile object during execution that are not specified.

This is a difference that makes no difference.  It can therefore be
ignored with impunity.  Or do you have some way to tell, within
strictly conforming code, that the compiler generated code that is
not strictly in accordance with the virtual machine model?

When it comes to figuring out what `register volatile v' means (versus
simply `register v'), consider the following:

  - `register' may not be applied to global and static variables.
  - The compiler (not the linker, nor anything else) chooses the
    location of all automatic (stack) variables.

This therefore implies that the compiler can easily tell whether the
register or memory it chooses for that variable is in fact volatile,
and can (and in all compilers that now exist---if you can find one
where this is false, let me know---does) choose storage that is not
volatile.  [By the way, using mmap on your entire stack segment after
using up all the local machine registers is cheating.  :-) ]

A rather more shaky claim, yet one that is partly believable, is that
`auto volatile' is also never needed (with that one exception).  The
major difference is that one can take the address of a non-register
automatic; it *is* conceivable that one can feed the address of a
program variable to a device and have that device make use of it.  But
since the compiler chooses the address of a register variable AND DOES
NOT EVER LET YOU KNOW WHAT IT IS, there is no way ever to get a handle
on that variable.  The compiler can assign it `real memory' space and
rest assured that that space is indeed not volatile.

None of the above applies to `register volatile *' (where the pointer
itself is not the volatile object, the volatility being in the object
to which it points).
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

w-colinp@microsoft.UUCP (Colin Plumb) (01/05/89)

I can think of at least one example of a register volatile variable, if
you're playing with setjmp/longjmp.  We all know a non-volatile variable
can't be trusted across a setjmp, so given

register int x, y;
jmp_buf jbuf;

x = 0;
y = setjmp(jbuf);
x++;
if (y = 0)
	longjmp(jbuf, 1);
printf("x = %d\n", x);

This "should" print 2, but a compiler could legitimately print x = 1.
Obviously, x isn't aliased (register), and flow analysis (which isn't
required to understand setjmp and longjmp) shows it is set to 0, incremented
once, and printed.  

"Register volatile x" tells the compiler to try and store it somewhere
quick, but safe from setjmp/longjmp.  In most compilers, this would
basically turn off the register declaration, but in transputer compilers
I've used, "register" tells the compiler to keep the value in the 16 words
nearest the stack pointer, which are especially quick to access.  Here,
register and volatile both mean something.

Can anyone poke holes in this example?
-- 
	-Colin (uunet!microsof!w-colinp)