mjs@rabbit.UUCP (M. J. Shannon, Jr.) (10/15/84)
<Eat this, Bug!> > > [Compiler is optimizing out a wait-for-hardware-done loop.] > > ... > > 1. Was this legal code generation? > > 2. Note that this compiler did "simple" optimizations as part of the code > > generation. Is this legal? > > As to whether it's legal by K&R, the only answer is "mumble". This > thorny issue was never addressed in the old days. The draft ANSI standard > has a "volatile" declaration that you can use to tell the compiler "don't > get tricky with this variable, it may change underfoot". First, I'd like to point out that the name of the standards organization is ANSI: American National Standards Institute. The name is unrelated to the name of the character set most of us are using (ASCII: American Standard Code for Information Interchange). Now, with that out of the way.... Ok, given the `volatile' keyword, what are its semantics? If (on System V) I have pointers into memory that is shared by other processes, must I declare them `volatile struct foo *' and have the compiler deduce that all members of such a structure are volatile (my opinion of correct behavior)? Further, if the structure so referenced has pointers in it, are they assumed to be volatile or non-volatile? If my program manages linked lists in shared memory, must the forward and backward pointers also be declared volatile? If so, this means that I may suffer from non-optimization of linked lists (identically declared) in non-shared memory. Volatility declarations are very tricky in a language such as C! Is a current draft available (sorry, I haven't been following this discussion due to (what seems to me to be) an inordinate amount of drivel)? If so, how may I obtain it? -- Marty Shannon UUCP: {alice,rabbit,research}!mjs (rabbit is soon to die. Does this mean alice is pregnant? Yup!) Phone: 201-582-3199
jss@sftri.UUCP (J.S.Schwarz) (10/15/84)
> Ok, given the `volatile' keyword, what are its semantics? ... > Volatility declarations are very tricky in a language such as C! Yes the semantics are tricky and the current draft is not completely clear. I have been thinking about this problem for several months and I believe there is an approach that, while not resolving all confusion, provides a framework in which most of the question can be consistently resolved. First, it must be clearly understood that "volatility" is a property of compile time expressions, not runtime data. Whenever such an expression occurs the compiler is obliged to generate code that implements the "raw" semantics of C. It must generate exactly as many fetches/stores as are required by the semantics of C for the expression as a whole. With one exception, whether fetches/stores are required in evaluating subexpressions are controlled by whether the subexpressions are themselves volatile. The exception is that evaluation of "&foo" where foo is a simple variable is never permitted to fetch "foo". How do we know if an expression has the "volatility" property? The easiest answer is to make it part of the type. To examine some of the implications of this approach, consider char nonvolatile_global; volatile char volatile_global; struct members { char nonvolatile_member ; char volatile_member ; } ; volatile struct members volatile_head, *ptr ; struct members nonvolatile_head ; Let us look at some expressions that might be used as a function argument and ask what fetches the compiler is required to generate. ------------------ volatile_global volatile_head *ptr These imply a fetch of the data. In the case of volatile_head and *ptr, this means all data contained in the structure. ------------------ volatile_head.volatile_member ptr->volatile_member My reading of the current draft would require that the whole struct be fetched in both cases and that the char be extracted from the value without an additional access. ------------------ nonvolatile_head.volatile_member A single fetch of the single char is required. ------------------ (volatile char)nonvolatile_global This expression does not require a fetch. The expression is volatile so we must conform to the C semantics in evaluating it. That means we must evaluate "nonvolatile_global" and then convert the value. But the cast applies to the value, not to the "name" and thus we do not have to do a fetch of the name. But evaluating "nonvalatile_global" does not force a fetch since that expression is not volatile. ------------------ *(volatile char*)&nonvolatile_global *&volatile_global These slightly "tricky" expressions require exactly one fetch. The first is volatile because of the cast and the semantics of C requires a fetch. The standard is not explicit whether the the subexpression of a "&" is evaluated. (Except in the presence of "volatile" it doesn't matter) I think that the reasonable interpretation is that only as much evaluation is performed as is needed to determine the pointer. In particular "&volatile_global" does not permit an evaluation of "volatile_global". I believe that this case is important enough that an explicit provision should be made that evaluation of "&foo" never fetches foo, whether or not foo is declared volatile. ------------------ Some of the above expressions are also lvalues. Let us examine them in the context of a left hand side of an expression: ------------------ volatile_global volatile_head *ptr *(volatile char*)&nonvolatile_global In these cases stores are requied exactly where fetches were above. ------------------ volatile_head.volatile_member ptr->volatile_member I am not completely sure, but my reading of the draft is that only the char is required to be stored not the entire structure, and no fetches are allowed. A plausible alternative reaing would be that the structure as a whole must be fetched and the only the char stored. ------------------ A problem remains. Does the use of "volatile" imply anything about the absence of fetches/stores not implied by the "raw" semantics of C. This is harder to answer for two reasons. Firstly there are many cases where an area of memory can be touched in nonobvious ways. Secondly, machine architectures may make it impossilbe. For example, on some machines it may be impossible to fetch/store a char without touching neighboring values. Thus stores of head.nonvolatile_member may unavoidably imply stores of head.volatile_member. Similar problems arise with regard to the atomicity of fetches/stores of volatile data. I think the best that can be asked for is a "good faith" effort on the part of the compiler, and a requirement that its behavior be documented. In the end it seems likely that the standard will not be completely clear about all cases. This is probably inevitable in a standard that is not based on a formal semantics. Nonetheless, I am completely convinced that the introduction of the "volatile" specifier is a good idea. Portability of any program that uses a "shared variable" is chancy at best whether or not a declaration is present. The more concerned about portability the programmer is, the more conservative s/he ought to be about the use of "shared variables". Two sensible rules might be: declare "volatile long" a variable that is used to communicate between a function that catches signals and the rest of a program, and never declare volatile members. This is very much in the "spirit" of C, which in part requires that portability is the result of care by the programmer, not a magical consequence of using C. In the absence of "volatile" programmers might be able to force the fetches/stores they require by such hacks as using expressions that are too complicated for the optimizer to understand, turning off optimization, using "asm" or modifying the compiler. But these hacks are likely to be much less portable than volatile declarations, even if there is some variation in the interpretation of the doubtful cases discussed above. Jerry Schwarz ihnp4!btlunix!jss Bell Labs, 190 River Road, Summit N.J., 07901 P.S. The latest draft of the ANSI proposal is available from Jack Warner ihnp4!btlunix!jlw Bell Labs, Room F-325, 190 River Road, Summit, N.J. 07901