Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > miscompilation of volatiles?

Reply
Thread Tools

miscompilation of volatiles?

 
 
John Regehr
Guest
Posts: n/a
 
      01-03-2008
I'm trying to figure out what -- if any -- ammunition the C standard
gives me for identifying bugs in the translation of C code containing
volatile variables. For example consider this fragment:

volatile int g_1;

void self_assign (void)
{
g_1 = g_1;
}

One of gcc's embedded ports translates this into the following asm
when invoked with -Os:

self_assign:
ret

On the other hand, when optimizations are disabled the same compiler
produces object code that properly loads from g_1 and then stores the
loaded value back into g_1.

At an informal level, the optimized code is obviously wrong in the
sense that any embedded C programmer would expect this function to
load from g_1 and then store back to it.

What I am trying to figure out is, is this output buggy from the
language lawyer point of view? On one hand the standard tells us that
"any expression referring to such an object shall be evaluated
strictly according to the rules of the abstract machine." This
appears to clearly call for a load and then a store. On the other
hand the standard also says "What constitutes an access to an object
that has volatile-qualified type is implementation-defined." This
seems to admit an implementation that specifies that volatile-
qualified types are never accessed, regardless of what the source code
looks like. But is it legal for the compiler to access the object at
some optimization levels and not at others?

Any help appreciated.
 
Reply With Quote
 
 
 
 
Peter Nilsson
Guest
Posts: n/a
 
      01-03-2008
John Regehr <(E-Mail Removed)> wrote:
> I'm trying to figure out what -- if any -- ammunition the C
> standard gives me for identifying bugs in the translation of
> C code containing volatile variables.


The ammunition you have is the standard itself, and the
implementation documentation.

>*For example consider this fragment:
>
> volatile int g_1;


I think you're unlikely to find any compile that will treat
'access' to this variable any differently from any other
non-volatile global. Since it is the compiler that allocates
space, it can determine whether there are any inherent
access considerations involved. As I said, I think most
compilers will not treat this declaration as anything
special beyond the volatile qualification in and of itself.

> void self_assign (void)
> {
> * g_1 = g_1;
> }
>
> One of gcc's embedded ports translates this into the
> following asm when invoked with -Os:
>
> self_assign:
> * * * * ret
>
> On the other hand, when optimizations are disabled the
> same compiler produces object code that properly loads
> from g_1 and then stores the loaded value back into g_1.


Which simply implies that self assignment will only be
optimised at higher levels.

Instead, try...

void self_assign (volatile int *vp)
{
*vp = *vp;
}

> At an informal level, the optimized code is obviously wrong
> in the sense that any embedded C programmer would expect
> this function to load from g_1 and then store back to it.


Would it? Even an embedded implementation would still be
managing control of the allocation. Note that many compilers
offer extensions to place variables in certain memory
addresses. Also note that access to memory mapped I/O (et al)
can (and is) also done via constructs like...

#define RESET (* (volatile unsigned short *) 0xFFFE)

> What I am trying to figure out is, is this output buggy from
> the language lawyer point of view?


No.

>*On one hand the standard tells us that "any expression
> referring to such an object shall be evaluated strictly
> according to the rules of the abstract machine." *This
> appears to clearly call for a load and then a store.


It's valid for the optimised version under the 'as if' rule.
Note that you cannot modify your program to detect the
optimisation and remain strictly conforming.

>*On the other hand the standard also says "What constitutes
> an access to an object that has volatile-qualified type is
> implementation-defined."


Precisely, which is why you may be better off posting your
question to a gcc group.

>*This seems to admit an implementation that specifies that
> volatile- qualified types are never accessed, regardless
> of what the source code looks like.


Not quite.

>*But is it legal for the compiler to access the object at
> some optimization levels and not at others?


Yes. As pointed out, all you've demonstrated is that higher
optimisation level will implement a no-op more efficiently.

--
Peter
 
Reply With Quote
 
 
 
 
John Regehr
Guest
Posts: n/a
 
      01-03-2008
On Jan 2, 5:58 pm, Peter Nilsson <(E-Mail Removed)> wrote:

> I think you're unlikely to find any compile that will treat
> 'access' to this variable any differently from any other
> non-volatile global. Since it is the compiler that allocates
> space, it can determine whether there are any inherent
> access considerations involved. As I said, I think most
> compilers will not treat this declaration as anything
> special beyond the volatile qualification in and of itself.


On the contrary, Microsoft, Intel, CodeWarrior, and all gcc4-based
compilers that I have tried turn the code I sent into a load then a
store. (The platform in question here, msp430, is not yet supported
by gcc4.)

Anyway I do not buy your reasoning. The volatile qualifier tells the
compiler not to perform precisely the kind of reasoning about storage
that you describe here.

> Note that you cannot modify your program to detect the
> optimisation and remain strictly conforming.


But I can, like this:

extern volatile int g_1;

void self_assign (void)
{
g_1 = g_1;
}

Now the storage allocation is not under control of the compiler, which
still emits the function doing nothing. I can detect the optimization
by using the linker to place g_1 onto a memory-mapped I/O register.

> Precisely, which is why you may be better off posting your
> question to a gcc group.


They know about this problem... I'm trying to get this all straight
because I do research in tools for embedded systems, not because I
want to work around specific gcc bugs.

John Regehr
 
Reply With Quote
 
Peter Nilsson
Guest
Posts: n/a
 
      01-03-2008
John Regehr <(E-Mail Removed)> wrote:
> ... *The volatile qualifier tells the compiler not to
> perform precisely the kind of reasoning about storage
> that you describe here.


There is only 1 scenario where the standard actually
precludes certain optimisations relating to volatile
objects, and that's in relation to automatic storage
in the presence of setjmp().[1] The only other case that
comes close is that objects of type sig_atomic_t type
may need to be volatile to work properly.

But appart from those, you can remove all volatile
qualifiers from any strictly conforming program and
not change the semantics. This is all the standard
cares about. Optimisation at the assembler level is
not defined by the standards. It's a QoI issue.

[1] It's not actually phrased in that way, rather it
says that in the presence of setjmp() non-volatile
automatic objects in the calling original function
have indeterminate values.

> > Note that you cannot modify your program to detect the
> > optimisation and remain strictly conforming.

>
> But I can, like this:
>
> extern volatile int g_1;


This would not change the semantics of a strictly conforming
program. That it changes the assembler code is immaterial
so long as the semantics are preserved.

> void self_assign (void)
> {
> * g_1 = g_1;
> }
>
> Now the storage allocation is not under control of the
> compiler, which still emits the function doing nothing.
>*I can detect the optimization by using the linker to place
> g_1 onto a memory-mapped I/O register.


If you're going to define variables other than through
standard source definitions, then you are not talking about
standard C. In which case, the standard doesn't have any
guarantees, and clc cannot assist you.

We can only describe what the standard requires from the
virtual machine. However, the standard leaves the definition
of 'access' to the implementation.

> > Precisely, which is why you may be better off posting
> > your question to a gcc group.

>
> They know about this problem...


You haven't demonstrated a problem. At least as far as the
standard is concerned.

> I'm trying to get this all straight because I do research
> in tools for embedded systems, not because I want to work
> around specific gcc bugs.


It sounds like your research is delving into aspects of
C implementations that are outside the scope of the C
standard.

--
Peter
 
Reply With Quote
 
Jack Klein
Guest
Posts: n/a
 
      01-03-2008
On Wed, 2 Jan 2008 16:02:12 -0800 (PST), John Regehr
<(E-Mail Removed)> wrote in comp.lang.c:

> I'm trying to figure out what -- if any -- ammunition the C standard
> gives me for identifying bugs in the translation of C code containing
> volatile variables. For example consider this fragment:
>
> volatile int g_1;
>
> void self_assign (void)
> {
> g_1 = g_1;
> }
>
> One of gcc's embedded ports translates this into the following asm
> when invoked with -Os:
>
> self_assign:
> ret
>
> On the other hand, when optimizations are disabled the same compiler
> produces object code that properly loads from g_1 and then stores the
> loaded value back into g_1.
>
> At an informal level, the optimized code is obviously wrong in the
> sense that any embedded C programmer would expect this function to
> load from g_1 and then store back to it.
>
> What I am trying to figure out is, is this output buggy from the
> language lawyer point of view? On one hand the standard tells us that
> "any expression referring to such an object shall be evaluated
> strictly according to the rules of the abstract machine." This
> appears to clearly call for a load and then a store. On the other
> hand the standard also says "What constitutes an access to an object
> that has volatile-qualified type is implementation-defined." This
> seems to admit an implementation that specifies that volatile-
> qualified types are never accessed, regardless of what the source code
> looks like. But is it legal for the compiler to access the object at
> some optimization levels and not at others?
>
> Any help appreciated.


In my opinion, the compiler is broken at the optimization level where
it omits the read and subsequent write of the object. For embedded
use, I would either demand a fix from the vendor or dump the compiler.

The bit in the standard about volatile access being
implementation-defined has been explained at least once by one member
of the committee, on comp.std.c, as referring to cases like the
following:

unsigned char some_func(volatile unsigned char *vucp)
{
return *vucp;
}

Now assuming 8-bit bytes and a typical processor with greater than
8-bit bus width, it is very likely that more than just the first byte
addressed by the pointer will be read. Quite likely, 32, 64, or more
bits will actually be read.

So if vucp actually points to an array of 8-bit hardware registers,
each of which experiences a side-effect when read, C does not
guarantee to only read vucp[0] and not to read vucp[1], vucp[2],
vucp[3], etc. Many processors that have wider busses always perform
full bus width reads, and only use individual byte-enable lines on
writes.

On quite a few hardware platforms, the following function:

void func2(volatile unsigned char *vucp, int x)
{
*vucp = x;
}

....will cause a read of multiple bytes, a change to one of those
bytes, and a rewrite of all of them, one with the new value and the
others with the values read.

So the expressed intent by a member of the committee covers the fact
that hardware may generate more physical (not logical) accesses than
one might expect from just looking at an expression. It is not
intended to allow the compiler to avoid specifically programmed
accesses.

It's unfortunate that so far nobody has been able to generate any
impetus to get this cleaned up.

Nevertheless, an expression that modifies the value of a volatile
object, especially one defined outside the current translation unit,
must be performed, based on the first 3 paragraphs of 5.1.2.3:

"1 The semantic descriptions in this International Standard describe
the behavior of an abstract machine in which issues of optimization
are irrelevant.

2 Accessing a volatile object, modifying an object, modifying a file,
or calling a function that does any of those operations are all side
effects, which are changes in the state of the execution environment.
Evaluation of an expression may produce side effects. At certain
specified points in the execution sequence called sequence points,all
side effects of previous evaluations shall be complete and no side
effects of subsequent evaluations shall have taken place. (A summary
of the sequence points is given in annex C.)

3 In the abstract machine, all expressions are evaluated as specified
by the semantics. An actual implementation need not evaluate part of
an expression if it can deduce that its value is not used and that no
needed side effects are produced (including any caused by calling a
function or accessing a volatile object)."

If the volatile object is defined externally, there is no way that the
compiler can guarantee that it can accurately deduce that the two
accesses of the volatile object produce no needed side effects.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 
Reply With Quote
 
John Regehr
Guest
Posts: n/a
 
      01-03-2008
> It sounds like your research is delving into aspects of
> C implementations that are outside the scope of the C
> standard.


I see your point. In that case it is depressing that the standard
describes a language that is useless for implementing operating
systems and embedded systems -- domains where C dominates and has no
obvious successors.

John Regehr
 
Reply With Quote
 
John Regehr
Guest
Posts: n/a
 
      01-03-2008
> If the volatile object is defined externally, there is no way that the
> compiler can guarantee that it can accurately deduce that the two
> accesses of the volatile object produce no needed side effects.


Thanks Jack. Certainly I prefer this interpretation as it leads to a
far more useful language.

As a side note, for most compilers that we have tested, we are finding
corner cases -- generally much more complex than the self-assignment
function I sent -- that are miscompiled at high optimization levels.

> In my opinion, the compiler is broken at the optimization level where
> it omits the read and subsequent write of the object. For embedded
> use, I would either demand a fix from the vendor or dump the compiler.


Right on. This port has other significant problems too. But it is
the only free compiler for an otherwise nice super-low-power 16-bit
architecture .

John Regehr
 
Reply With Quote
 
Chris Torek
Guest
Posts: n/a
 
      01-03-2008
In article <(E-Mail Removed)>
John Regehr <(E-Mail Removed)> wrote:

[snippage of actual code, but the problem is that one version of
gcc removes entirely two references to a "volatile"-qualified object]

>At an informal level, the optimized code is obviously wrong in the
>sense that any embedded C programmer would expect this function to
>load from g_1 and then store back to it.


Indeed, and as others have mentioned in this thread, this is in fact
a bug in that gcc port.

>What I am trying to figure out is, is this output buggy from the
>language lawyer point of view?


Most likely (and in the case of gcc, yes, although given the state of
some gcc documentation ... well, read on), but:

>On one hand the standard tells us that
>"any expression referring to such an object shall be evaluated
>strictly according to the rules of the abstract machine." This
>appears to clearly call for a load and then a store. On the other
>hand the standard also says "What constitutes an access to an object
>that has volatile-qualified type is implementation-defined."


What this means is that, in order to *prove* that this is a bug in
the implementation, you must read the documentation that the
implementation is required to provide. Somewhere in this documentation,
there must be some text defining "what constitutes an access to an
object that has volatile-qualified type". Use that wording, plus
the standard's "abstract machine" requirements, to prove the bug.

>This seems to admit an implementation that specifies that volatile-
>qualified types are never accessed, regardless of what the source code
>looks like.


If the compiler documentation says that, the compiler is doing the
"right thing" and it is not a bug. But it (this compiler) is then
worthless for much embedded work, and -- as was once said of a book
-- is not to be put aside lightly, but rather thrown with great
force. Of course, the documentation does not say that. A
"good" implementation will say something much more obvious and
appropriate for the target system, and you can then use that to
prove that the compiler is broken.

Given that this is some unspecified port of some unspecified version
of gcc, the documentation may well be missing outright, though.
On the bright side, you will presumably have the gcc source, and
can go fix the bug. (In older versions of gcc, at least, this is
just a matter of testing the "volatile" flag and skipping the
unwanted optimization if it is set. The trick is finding the
place(s) that is/are doing this inappropriate optimization -- and
finding the "volatile" flag, which as I recall is not at all
obvious.)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (4039.22'N, 11150.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
 
Reply With Quote
 
Chris Thomasson
Guest
Posts: n/a
 
      01-03-2008
"Jack Klein" <(E-Mail Removed)> wrote in message
news:(E-Mail Removed)...
> On Wed, 2 Jan 2008 16:02:12 -0800 (PST), John Regehr
> <(E-Mail Removed)> wrote in comp.lang.c:
>
>> I'm trying to figure out what -- if any -- ammunition the C standard
>> gives me for identifying bugs in the translation of C code containing
>> volatile variables. For example consider this fragment:
>>
>> volatile int g_1;
>>
>> void self_assign (void)
>> {
>> g_1 = g_1;
>> }
>>
>> One of gcc's embedded ports translates this into the following asm
>> when invoked with -Os:
>>
>> self_assign:
>> ret
>>
>> On the other hand, when optimizations are disabled the same compiler
>> produces object code that properly loads from g_1 and then stores the
>> loaded value back into g_1.
>>
>> At an informal level, the optimized code is obviously wrong in the
>> sense that any embedded C programmer would expect this function to
>> load from g_1 and then store back to it.
>>
>> What I am trying to figure out is, is this output buggy from the
>> language lawyer point of view? On one hand the standard tells us that
>> "any expression referring to such an object shall be evaluated
>> strictly according to the rules of the abstract machine." This
>> appears to clearly call for a load and then a store. On the other
>> hand the standard also says "What constitutes an access to an object
>> that has volatile-qualified type is implementation-defined." This
>> seems to admit an implementation that specifies that volatile-
>> qualified types are never accessed, regardless of what the source code
>> looks like. But is it legal for the compiler to access the object at
>> some optimization levels and not at others?
>>
>> Any help appreciated.

>
> In my opinion, the compiler is broken at the optimization level where
> it omits the read and subsequent write of the object.

[...]

This is not only problem with GCC and threading:

http://groups.google.com/group/comp....f6360d939612b3

:^0

 
Reply With Quote
 
Randy Howard
Guest
Posts: n/a
 
      01-03-2008
On Wed, 2 Jan 2008 23:09:16 -0600, John Regehr wrote
(in article
<(E-Mail Removed)>):

>> It sounds like your research is delving into aspects of
>> C implementations that are outside the scope of the C
>> standard.

>
> I see your point. In that case it is depressing that the standard
> describes a language that is useless for implementing operating
> systems and embedded systems -- domains where C dominates and has no
> obvious successors.


Given that practically /all/ operating systems and the vast majority of
embedded systems use C, how can your statement possibly be true?

--
Randy Howard (2reply remove FOOBAR)
"The power of accurate observation is called cynicism by those
who have not got it." - George Bernard Shaw





 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
systematic miscompilation of volatile accesses John Regehr C Programming 0 04-29-2008 03:30 AM



Advertisments