diff mbox

[gimple] assignments to volatile

Message ID 4C1F5380.1090107@codesourcery.com
State New
Headers show

Commit Message

Nathan Sidwell June 21, 2010, 11:56 a.m. UTC
I've come across inconsistent and surprising behaviour when assigning to 
volatile lvalues.

Sometimes we re-read the assigned-to object, and sometimes we do not.  For instance,
    return vobj = data;
will cause a reread of vobj, IF data is not a constant.
or
   cond ? vobj = data : 0
will cause a reread of vobj, even when we don't use the result of the 
conditional expression -- unless data is a constant, in which case there is 
no-reread.  (And confusingly, if you put void casts inside the two conditional 
sub-expressions, the re-reading goes away, but it doesn't even when you 
explicitly cast the whole expression to void).

In the attached testcase, test_1, test_6 and test_7 have this surprising 
rereading behaviour.

The fault appears to be in gimplify_modify_expr, where we return the LHS as the 
value, if the caller wants the value.  The attached patch changes that routine 
when the target lvalue is volatile.  In that case it will create a temporary to 
hold the RHS value, assign that temporary to the LHS and then return the temporary.

Although the bug is architecture-neutral, the testcase is x86-specific because 
it's checking for specific accesses occurring.

built and tested on i686-pc-linux-gnu, ok?

nathan

Comments

Richard Biener June 21, 2010, 12:07 p.m. UTC | #1
On Mon, Jun 21, 2010 at 1:56 PM, Nathan Sidwell <nathan@codesourcery.com> wrote:
> I've come across inconsistent and surprising behaviour when assigning to
> volatile lvalues.
>
> Sometimes we re-read the assigned-to object, and sometimes we do not.  For
> instance,
>   return vobj = data;
> will cause a reread of vobj, IF data is not a constant.
> or
>  cond ? vobj = data : 0
> will cause a reread of vobj, even when we don't use the result of the
> conditional expression -- unless data is a constant, in which case there is
> no-reread.  (And confusingly, if you put void casts inside the two
> conditional sub-expressions, the re-reading goes away, but it doesn't even
> when you explicitly cast the whole expression to void).
>
> In the attached testcase, test_1, test_6 and test_7 have this surprising
> rereading behaviour.
>
> The fault appears to be in gimplify_modify_expr, where we return the LHS as
> the value, if the caller wants the value.  The attached patch changes that
> routine when the target lvalue is volatile.  In that case it will create a
> temporary to hold the RHS value, assign that temporary to the LHS and then
> return the temporary.
>
> Although the bug is architecture-neutral, the testcase is x86-specific
> because it's checking for specific accesses occurring.
>
> built and tested on i686-pc-linux-gnu, ok?

Hm, TREE_THIS_VOLATILE (TREE_TYPE (*to_p)) should
certainly be TREE_THIS_VOLATILE (*to_p), and all this
should be done after gimplifying *to_p.  I think even just
before we build the gimple assign/call do

if (TREE_THIS_VOLATILE (*to_p) && want_value)
  *from_p = get_initialized_tmp_var (*from_p, pre_p, post_p);

and change

  if (want_value)
    {
      *expr_p = unshare_expr (*to_p);
      return GS_OK;

to

  if (want_value)
    {
      *expr_p = TREE_THIS_VOLATILE (*to_p) ? *from_p : unshare_expr (*to_p);
      return GS_O;

Richard.
Michael Matz June 21, 2010, 1:30 p.m. UTC | #2
Hello,

On Mon, 21 Jun 2010, Richard Guenther wrote:

> > I've come across inconsistent and surprising behaviour when assigning 
> > to volatile lvalues.
> >
> > Sometimes we re-read the assigned-to object, and sometimes we do not.  For
> > instance,
> >   return vobj = data;
> > will cause a reread of vobj, IF data is not a constant.

I'd consider the latter condition a bug.  I.e. the rereading must happen 
in every case.  It must happen because in C the value of "lhs = rhs" is 
that of reading 'lhs' ("An assignment expression has the value of the left 
operand after the assignment, but is not an lvalue." 6.5.16), and if lhs 
happens to be volatile than the abstract machine implies two accesses to 
lhs, one write (of the assignment) and one read (to get to the value of 
lhs).

> > or
> >  cond ? vobj = data : 0
> > will cause a reread of vobj, even when we don't use the result of the
> > conditional expression -- unless data is a constant, in which case there is
> > no-reread.  (And confusingly, if you put void casts inside the two
> > conditional sub-expressions, the re-reading goes away, but it doesn't even
> > when you explicitly cast the whole expression to void).
> >
> > In the attached testcase, test_1, test_6 and test_7 have this surprising
> > rereading behaviour.
> >
> > The fault appears to be in gimplify_modify_expr, where we return the LHS as
> > the value, if the caller wants the value.  The attached patch changes that
> > routine when the target lvalue is volatile.  In that case it will create a
> > temporary to hold the RHS value, assign that temporary to the LHS and then
> > return the temporary.

... which implies that I think that this is exactly the wrong solution.


Ciao,
Michael.
Richard Biener June 21, 2010, 1:40 p.m. UTC | #3
On Mon, Jun 21, 2010 at 3:30 PM, Michael Matz <matz@suse.de> wrote:
> Hello,
>
> On Mon, 21 Jun 2010, Richard Guenther wrote:
>
>> > I've come across inconsistent and surprising behaviour when assigning
>> > to volatile lvalues.
>> >
>> > Sometimes we re-read the assigned-to object, and sometimes we do not.  For
>> > instance,
>> >   return vobj = data;
>> > will cause a reread of vobj, IF data is not a constant.
>
> I'd consider the latter condition a bug.  I.e. the rereading must happen
> in every case.  It must happen because in C the value of "lhs = rhs" is
> that of reading 'lhs' ("An assignment expression has the value of the left
> operand after the assignment, but is not an lvalue." 6.5.16), and if lhs
> happens to be volatile than the abstract machine implies two accesses to
> lhs, one write (of the assignment) and one read (to get to the value of
> lhs).
>
>> > or
>> >  cond ? vobj = data : 0
>> > will cause a reread of vobj, even when we don't use the result of the
>> > conditional expression -- unless data is a constant, in which case there is
>> > no-reread.  (And confusingly, if you put void casts inside the two
>> > conditional sub-expressions, the re-reading goes away, but it doesn't even
>> > when you explicitly cast the whole expression to void).
>> >
>> > In the attached testcase, test_1, test_6 and test_7 have this surprising
>> > rereading behaviour.
>> >
>> > The fault appears to be in gimplify_modify_expr, where we return the LHS as
>> > the value, if the caller wants the value.  The attached patch changes that
>> > routine when the target lvalue is volatile.  In that case it will create a
>> > temporary to hold the RHS value, assign that temporary to the LHS and then
>> > return the temporary.
>
> ... which implies that I think that this is exactly the wrong solution.

And also implies that the frontend should arrange for what
gets used.

Richard.

>
> Ciao,
> Michael.
Nathan Sidwell June 21, 2010, 1:54 p.m. UTC | #4
On 06/21/10 14:30, Michael Matz wrote:

> I'd consider the latter condition a bug.  I.e. the rereading must happen
> in every case.  It must happen because in C the value of "lhs = rhs" is
> that of reading 'lhs' ("An assignment expression has the value of the left
> operand after the assignment, but is not an lvalue." 6.5.16), and if lhs
> happens to be volatile than the abstract machine implies two accesses to
> lhs, one write (of the assignment) and one read (to get to the value of
> lhs).

The subexpression 'vobj = expr' has a value.  Shouldn't there therefore be a 
reread of that value in the expression-statement 'vobj = expr;'?

nathan
Iain Sandoe June 21, 2010, 2:05 p.m. UTC | #5
On 21 Jun 2010, at 14:30, Michael Matz wrote:
> On Mon, 21 Jun 2010, Richard Guenther wrote:
>
>>> I've come across inconsistent and surprising behaviour when  
>>> assigning
>>> to volatile lvalues.
>>>
>>> Sometimes we re-read the assigned-to object, and sometimes we do  
>>> not.  For
>>> instance,
>>>   return vobj = data;
>>> will cause a reread of vobj, IF data is not a constant.
>
> I'd consider the latter condition a bug.  I.e. the rereading must  
> happen
> in every case.  It must happen because in C the value of "lhs = rhs"  
> is
> that of reading 'lhs' ("An assignment expression has the value of  
> the left
> operand after the assignment, but is not an lvalue." 6.5.16), and if  
> lhs
> happens to be volatile than the abstract machine implies two  
> accesses to
> lhs, one write (of the assignment) and one read (to get to the value  
> of
> lhs).

that answers the "what" but not the "when".

Suppose that vobj was a hardware register ...

... I would expect that the assignment would write the value of expr  
to the register...

... but I would _not_ expect the register to be re-read (and, indeed,  
in many cases in hardware interaction - that could cause an issue).

or did I miss sth?
Iain
Michael Matz June 21, 2010, 2:07 p.m. UTC | #6
Hi,

On Mon, 21 Jun 2010, Nathan Sidwell wrote:

> On 06/21/10 14:30, Michael Matz wrote:
> 
> > I'd consider the latter condition a bug.  I.e. the rereading must happen
> > in every case.  It must happen because in C the value of "lhs = rhs" is
> > that of reading 'lhs' ("An assignment expression has the value of the left
> > operand after the assignment, but is not an lvalue." 6.5.16), and if lhs
> > happens to be volatile than the abstract machine implies two accesses to
> > lhs, one write (of the assignment) and one read (to get to the value of
> > lhs).
> 
> The subexpression 'vobj = expr' has a value.  Shouldn't there therefore be a
> reread of that value in the expression-statement 'vobj = expr;'?

A good question.  I'm very certain that we need a reread for
 "x = (vobj = y);" .
(I even thought we had testcases for this actually happening)
I would find the consequence of also having to reread for "vobj = y;" 
surprising.

There's some leeway in the standard as to what actually constitutes an 
access to a volatile object (implementation-defined), and perhaps we can 
define it to be no (read) access in the second case: The subexpression has 
a value, but the source code doesn't retrieve it, and I think we're free 
to define this non-retrieval to imply no access to the value (and hence to 
vobj).


Ciao,
Michael.
Richard Biener June 21, 2010, 2:08 p.m. UTC | #7
On Mon, Jun 21, 2010 at 4:07 PM, Michael Matz <matz@suse.de> wrote:
> Hi,
>
> On Mon, 21 Jun 2010, Nathan Sidwell wrote:
>
>> On 06/21/10 14:30, Michael Matz wrote:
>>
>> > I'd consider the latter condition a bug.  I.e. the rereading must happen
>> > in every case.  It must happen because in C the value of "lhs = rhs" is
>> > that of reading 'lhs' ("An assignment expression has the value of the left
>> > operand after the assignment, but is not an lvalue." 6.5.16), and if lhs
>> > happens to be volatile than the abstract machine implies two accesses to
>> > lhs, one write (of the assignment) and one read (to get to the value of
>> > lhs).
>>
>> The subexpression 'vobj = expr' has a value.  Shouldn't there therefore be a
>> reread of that value in the expression-statement 'vobj = expr;'?
>
> A good question.  I'm very certain that we need a reread for
>  "x = (vobj = y);" .
> (I even thought we had testcases for this actually happening)
> I would find the consequence of also having to reread for "vobj = y;"
> surprising.
>
> There's some leeway in the standard as to what actually constitutes an
> access to a volatile object (implementation-defined), and perhaps we can
> define it to be no (read) access in the second case: The subexpression has
> a value, but the source code doesn't retrieve it, and I think we're free
> to define this non-retrieval to imply no access to the value (and hence to
> vobj).

The standard also specifies that the value has unqualified type
(whether that matters here or not I am not sure).  So x is assigned
from a non-volatile typed value.

Richard.
Michael Matz June 21, 2010, 2:09 p.m. UTC | #8
Hi,

On Mon, 21 Jun 2010, IainS wrote:

> > > >  return vobj = data;
> > > >will cause a reread of vobj, IF data is not a constant.
> >
> >I'd consider the latter condition a bug.  I.e. the rereading must happen
> >in every case.  It must happen because in C the value of "lhs = rhs" is
> >that of reading 'lhs' ("An assignment expression has the value of the left
> >operand after the assignment, but is not an lvalue." 6.5.16), and if lhs
> >happens to be volatile than the abstract machine implies two accesses to
> >lhs, one write (of the assignment) and one read (to get to the value of
> >lhs).
> 
> that answers the "what" but not the "when".
> 
> Suppose that vobj was a hardware register ...
> 
> ... I would expect that the assignment would write the value of expr to the
> register...
> 
> ... but I would _not_ expect the register to be re-read (and, indeed, in many
> cases in hardware interaction - that could cause an issue).
> 
> or did I miss sth?

Yes, namely that the author explicitely wrote a read access to vobj.
To demonstrate, like
  return vobj;
implies a read from vobj, also
  return vobj = x;
implies a read from vobj.  The latter _also_ implies a store to vobj.  If 
you want the read to not happen the author better writes
  vobj = x;
  return x;


Ciao,
Michael.
Nathan Sidwell June 21, 2010, 2:16 p.m. UTC | #9
On 06/21/10 15:09, Michael Matz wrote:

> Yes, namely that the author explicitely wrote a read access to vobj.
> To demonstrate, like
>    return vobj;
> implies a read from vobj, also
>    return vobj = x;
> implies a read from vobj.  The latter _also_ implies a store to vobj.  If
> you want the read to not happen the author better writes
>    vobj = x;
>    return x;

Then what about plain
	vobj;
?  Is that a read of vobj?

what about hiding the assignment inside a cond expr
      cond ? vobj = expr : 0;
Is that a re-read of vobj?

nathan
Michael Matz June 21, 2010, 2:17 p.m. UTC | #10
Hi,

On Mon, 21 Jun 2010, Richard Guenther wrote:

> >> The subexpression 'vobj = expr' has a value.  Shouldn't there 
> >> therefore be a reread of that value in the expression-statement 'vobj 
> >> = expr;'?
> >
> > A good question.  I'm very certain that we need a reread for  "x = 
> > (vobj = y);" . (I even thought we had testcases for this actually 
> > happening) I would find the consequence of also having to reread for 
> > "vobj = y;" surprising.
> >
> > There's some leeway in the standard as to what actually constitutes an
> > access to a volatile object (implementation-defined), and perhaps we can
> > define it to be no (read) access in the second case: The subexpression has
> > a value, but the source code doesn't retrieve it, and I think we're free
> > to define this non-retrieval to imply no access to the value (and hence to
> > vobj).
> 
> The standard also specifies that the value has unqualified type
> (whether that matters here or not I am not sure).  So x is assigned
> from a non-volatile typed value.

Yes, but I don't think this matters.  In 'int x = (int)vobj;' the value of 
the RHS will also be unqualified, but to get to that value you still have 
to access vobj.


Ciao,
Michael.
Nathan Sidwell June 21, 2010, 2:23 p.m. UTC | #11
On 06/21/10 15:09, Michael Matz wrote:

> implies a read from vobj, also
>    return vobj = x;
> implies a read from vobj.

Also, doesn't that contradict the sequence-point rules?  We'd have a read and 
write of vobj without an intervening sequence point (and the read was not used 
to determine the to-be-written value).

nathan
Michael Matz June 21, 2010, 2:25 p.m. UTC | #12
Hi,

On Mon, 21 Jun 2010, Nathan Sidwell wrote:

> > Yes, namely that the author explicitely wrote a read access to vobj.
> > To demonstrate, like
> >    return vobj;
> > implies a read from vobj, also
> >    return vobj = x;
> > implies a read from vobj.  The latter _also_ implies a store to vobj.  If
> > you want the read to not happen the author better writes
> >    vobj = x;
> >    return x;
> 
> Then what about plain
> 	vobj;
> ?  Is that a read of vobj?

As said, implementation-defined I think.  (I think we should define it to 
be no access, unlike 'x = vobj;' even if x is unused later.).

> what about hiding the assignment inside a cond expr
>      cond ? vobj = expr : 0;
> Is that a re-read of vobj?

If that's the full expression I think we should say that this doesn't 
constitute a read access.  If the above was itself only a subexpression it 
should be a read access, like in 
  x = cond ? vobj = expr : 0; or
  f (cond ? vobj = expr : 0);
(Of course both accesses would only happen if cond is true).

Put probably Joseph will correct me on all points I've made :-)


Ciao,
Michael.
Michael Matz June 21, 2010, 2:49 p.m. UTC | #13
Hi,

On Mon, 21 Jun 2010, Nathan Sidwell wrote:

> > implies a read from vobj, also
> >    return vobj = x;
> > implies a read from vobj.
> 
> Also, doesn't that contradict the sequence-point rules?  We'd have a 
> read and write of vobj without an intervening sequence point (and the 
> read was not used to determine the to-be-written value).

I think there's no problem.  We have three side-effects for the above 
expression: two accesses to vobj (one read (1), one write (2)), and the 
modification of object vobj (3).  The separation of the volatile write 
access and the actual modification of value may seem academic, but let's 
follow the letter.  All these side-effects must be complete before the 
next sequence point (we have only one here, the end of full expression), 
which will obviously be the case, no matter if there's side-effect (1) or 
not.

Now, onto 6.5 #2:

  Between the previous and next sequence point an object shall have its 
  stored value modified at most once by the evaluation of an expression. 
  Furthermore, the prior value shall be read only to determine the value 
  to be stored.70)

This talks only about the side-effect of changing the stored value (which 
we do only once) and about the prior value, which we don't use at all.

I.e. I think volatility doesn't enter the picture as far as 6.5. #2 is 
concerned, it rules out the usual pattern of "i=i++ + 1", no matter if i 
is volatile or not.


Ciao,
Michael.
Dave Korn June 22, 2010, 10:53 a.m. UTC | #14
On 21/06/2010 15:25, Michael Matz wrote:
> Hi,
> 
> On Mon, 21 Jun 2010, Nathan Sidwell wrote:
> 
>>> Yes, namely that the author explicitely wrote a read access to vobj.
>>> To demonstrate, like
>>>    return vobj;
>>> implies a read from vobj, also
>>>    return vobj = x;
>>> implies a read from vobj.  The latter _also_ implies a store to vobj.  If
>>> you want the read to not happen the author better writes
>>>    vobj = x;
>>>    return x;
>> Then what about plain
>> 	vobj;
>> ?  Is that a read of vobj?
> 
> As said, implementation-defined I think.  (I think we should define it to 
> be no access, unlike 'x = vobj;' even if x is unused later.).

  I'm not so sure, that's how people write code to read-and-discard
auto-resetting flag registers in memory mapped hardware.

    cheers,
      DaveK
Mark Mitchell June 22, 2010, 3:17 p.m. UTC | #15
Michael Matz wrote:

>>> Sometimes we re-read the assigned-to object, and sometimes we do not.  For
>>> instance,
>>>   return vobj = data;
>>> will cause a reread of vobj, IF data is not a constant.
> 
> I'd consider the latter condition a bug.  I.e. the rereading must happen 
> in every case.

I think that it's without question that:

  vobj = data;

should not cause a read from vobj; the value is not used.  I think
real-world embedded programmers would be surprised to see a read from an
I/O register there.  Do you agree?

Similarly, I think that:

  cond ? vobj = data : ...

should not cause a read from vobj.  The conditional in this case is just
a form of if/then; it would surprise programmers if this behaved
differently from:

  if (cond)
    vobj = data;

Do you agree?

The case of:

  x = vobj = data;

is different because the value is being used.  In that case, I have no
strong opinion.

Rather than argue about this last case in theory, though, I'd be
inclined to look at existing practice.  What do RealView, CodeWarrior,
Green Hills or other long-standing compilers for embedded systems do?
If there's a clear consensus, we can follow that.
Jakub Jelinek June 22, 2010, 3:21 p.m. UTC | #16
On Tue, Jun 22, 2010 at 11:17:31AM -0400, Mark Mitchell wrote:
>   if (cond)
>     vobj = data;
> 
> Do you agree?
> 
> The case of:
> 
>   x = vobj = data;
> 
> is different because the value is being used.  In that case, I have no
> strong opinion.

What about
(void) vobj;
or
(void) (vobj = data);
?  Should that read vobj?

	Jakub
Michael Matz June 22, 2010, 3:24 p.m. UTC | #17
Hi,

On Tue, 22 Jun 2010, Mark Mitchell wrote:

> I think that it's without question that:
> 
>   vobj = data;
> 
> should not cause a read from vobj; the value is not used.  I think
> real-world embedded programmers would be surprised to see a read from an
> I/O register there.  Do you agree?

Yes.

>   cond ? vobj = data : ...
> 
> should not cause a read from vobj.
> 
> Do you agree?

Yes.

> The case of:
>   x = vobj = data;
> 
> is different because the value is being used.  In that case, I have no
> strong opinion.
> 
> Rather than argue about this last case in theory, though, I'd be
> inclined to look at existing practice.  What do RealView, CodeWarrior,
> Green Hills or other long-standing compilers for embedded systems do?

That would be interesting, yes.

> If there's a clear consensus, we can follow that.


Ciao,
Michael.
Paul Koning June 22, 2010, 3:25 p.m. UTC | #18
> Michael Matz wrote:

> 

> >>> Sometimes we re-read the assigned-to object, and sometimes we do

> not.  For

> >>> instance,

> >>>   return vobj = data;

> >>> will cause a reread of vobj, IF data is not a constant.

> >

> > I'd consider the latter condition a bug.  I.e. the rereading must

> happen

> > in every case.

> 

> I think that it's without question that:

> 

>   vobj = data;

> 

> should not cause a read from vobj; the value is not used.  I think

> real-world embedded programmers would be surprised to see a read from

> an

> I/O register there.  Do you agree?


Yes.  Whatever else you do, this property is mandatory.
 
> Similarly, I think that:

> 

>   cond ? vobj = data : ...

> 

> should not cause a read from vobj.  The conditional in this case is

> just

> a form of if/then; it would surprise programmers if this behaved

> differently from:

> 

>   if (cond)

>     vobj = data;

> 

> Do you agree?


Maybe.  Personally I would view the statement you listed as bad code.
 
> The case of:

> 

>   x = vobj = data;

> 

> is different because the value is being used.  In that case, I have no

> strong opinion.

> 

> Rather than argue about this last case in theory, though, I'd be

> inclined to look at existing practice.  What do RealView, CodeWarrior,

> Green Hills or other long-standing compilers for embedded systems do?

> If there's a clear consensus, we can follow that.


Right.

The question on the statement you quoted is whether people interpret it as "data is assigned to two variables" or "data is assigned to vobj and the vobj is assigned to x".  I take it from earlier discussion that the standard says the latter.  Actually, given that this code is confusing, it might deserve a warning.

Finally, whatever is done must produce the same answer whether the code is compiled as C or C++.

	paul
Paul Koning June 22, 2010, 3:27 p.m. UTC | #19
> What about
> (void) vobj;
> or
> (void) (vobj = data);
> ?  Should that read vobj?

Yes on the first.  Don't know on the second.  The problem is that some
programmers have a bad habit of throwing in (void) when it isn't needed,
for example when ignoring the return value of a function used as a
statement.  Code like (void) printf (...) is quite common.  Given that
people do this, it's hard to guess what (void) (a=b); was intended to
convey.  Maybe the best thing to do with that one is (a) pick an answer,
(b) generate a warning.

	paul
Mark Mitchell June 22, 2010, 3:36 p.m. UTC | #20
Michael Matz wrote:

>>   x = vobj = data;

>> Rather than argue about this last case in theory, though, I'd be
>> inclined to look at existing practice.  What do RealView, CodeWarrior,
>> Green Hills or other long-standing compilers for embedded systems do?

> That would be interesting, yes.

Nathan, I know that we have RealView available for testing, and I
believe we also have CodeWarrior installed.  Would you like to give
those two a try?

If so, you could also look at:

  vobj;

and:

  (void) vobj;

I think those should do reads, but, again, I'd be persuaded by existing
practice.
Nathan Sidwell June 23, 2010, 10:35 a.m. UTC | #21
On 06/22/10 16:36, Mark Mitchell wrote:

> Nathan, I know that we have RealView available for testing, and I
> believe we also have CodeWarrior installed.  Would you like to give
> those two a try?

I couldn't get CodeWarrior to play.

RVCT generated code consistent with my proposal.  Specifically in:
  some_use_of = volatile_object = value;
the volatile object is only written to and not read, regardless of the context 
of that volatile assignment (be it return expression, conditional expression or 
serial assignment).

>    vobj;
>    (void) vobj;

Both of these cause a read of vobj in RVCT (and GCC).

nathan
Mark Mitchell June 23, 2010, 1:08 p.m. UTC | #22
Nathan Sidwell wrote:

> RVCT generated code consistent with my proposal.  Specifically in:
>  some_use_of = volatile_object = value;
> the volatile object is only written to and not read, regardless of the
> context of that volatile assignment (be it return expression,
> conditional expression or serial assignment).

Good.

>>    vobj;
>>    (void) vobj;
> 
> Both of these cause a read of vobj in RVCT (and GCC).

Good.

If there are no objections within the next 48 hours, then I think we
should consider the semantics settled.  I'm not trying to squash
discussion; if people don't like those semantics, let's discuss.  But, I
think existing practice should weigh heavily.

Thanks,
Michael Matz June 23, 2010, 1:10 p.m. UTC | #23
Hi,

On Wed, 23 Jun 2010, Mark Mitchell wrote:

> >>    vobj;
> >>    (void) vobj;
> > 
> > Both of these cause a read of vobj in RVCT (and GCC).
> 
> Good.
> 
> If there are no objections within the next 48 hours, then I think we 
> should consider the semantics settled.  I'm not trying to squash 
> discussion; if people don't like those semantics, let's discuss.  But, I 
> think existing practice should weigh heavily.

FWIW I agree.


Ciao,
Michael.
Richard Biener June 23, 2010, 2:01 p.m. UTC | #24
On Wed, Jun 23, 2010 at 3:10 PM, Michael Matz <matz@suse.de> wrote:
> Hi,
>
> On Wed, 23 Jun 2010, Mark Mitchell wrote:
>
>> >>    vobj;
>> >>    (void) vobj;
>> >
>> > Both of these cause a read of vobj in RVCT (and GCC).
>>
>> Good.
>>
>> If there are no objections within the next 48 hours, then I think we
>> should consider the semantics settled.  I'm not trying to squash
>> discussion; if people don't like those semantics, let's discuss.  But, I
>> think existing practice should weigh heavily.
>
> FWIW I agree.

And thus the revised patch is ok if it indeed implements the above
and passes bootstrap & regtest.

Thanks,
Richard.

>
> Ciao,
> Michael.
>
Paul Koning June 23, 2010, 2:20 p.m. UTC | #25
> Nathan Sidwell wrote:
> 
> > RVCT generated code consistent with my proposal.  Specifically in:
> >  some_use_of = volatile_object = value;
> > the volatile object is only written to and not read, regardless of
> the
> > context of that volatile assignment (be it return expression,
> > conditional expression or serial assignment).
> 
> Good.
> 
> >>    vobj;
> >>    (void) vobj;
> >
> > Both of these cause a read of vobj in RVCT (and GCC).
> 
> Good.
> 
> If there are no objections within the next 48 hours, then I think we
> should consider the semantics settled.  I'm not trying to squash
> discussion; if people don't like those semantics, let's discuss.  But,
> I
> think existing practice should weigh heavily.

Agreed.

The semantics will be the same for C and C++, correct?

	paul
Mark Mitchell June 23, 2010, 3:01 p.m. UTC | #26
Paul Koning wrote:

> The semantics will be the same for C and C++, correct?

I should certainly hope so.  I see no reason for them to be different,
and we certainly want to minimize such differences.
Mike Stump June 23, 2010, 6:36 p.m. UTC | #27
On Jun 23, 2010, at 6:08 AM, Mark Mitchell wrote:
> Nathan Sidwell wrote:
> 
>> RVCT generated code consistent with my proposal.  Specifically in:
>> some_use_of = volatile_object = value;
>> the volatile object is only written to and not read, regardless of the
>> context of that volatile assignment (be it return expression,
>> conditional expression or serial assignment).
> 
> Good.
> 
>>>   vobj;
>>>   (void) vobj;
>> 
>> Both of these cause a read of vobj in RVCT (and GCC).
> 
> Good.
> 
> If there are no objections within the next 48 hours,

I object.

gcc semantics are carefully designed.  gcc is the standard compiler for much embedded work, C and C++ differ due to the standards organizations, some of those differences are truly unfortunate, but there you go.  If someone wanted to fix gcc, the proper way would be to get the C and C++ committees on the same page, nail it down, or make it a hard error, and remove any latitude and confusion and make them agree.

C++ says:

  All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.

You can't just willy nilly change this:

int i, j, k;
void foo(int &j) {
  foo(i = k);
}

can sense if you get it right or not.  Add a volatile on i, and you discover, you can't just change this as you see fit, in particular, not in the way you all seem to want, which is just plain wrong.

Further, C says:

  An assignment expression has the value of the left operand after the assignment

Now, this is pretty plain, the value is the left operand.  They choose this wording as they didn't want the return value to be a modifiable lvalue.  I concede there is enough wiggle room here to confuse users and give implementors enough latitude to try and claim they are following the standard, but really, the return value is the valuer of the left operand, if it weren't, the standard would not say it was.  It could have said, the value is the value that was stored in the left operand, or the value is the right operand after conversion to the type of the left operand, or a host of other possibilities.

> then I think we should consider the semantics settled.  I'm not trying to squash
> discussion; if people don't like those semantics, let's discuss.  But, I
> think existing practice should weigh heavily.

This semantic in gcc has been the industry standard for at least 15 years.  If there have been any code written that depend upon this, you break them all.  This breakage is silent, and in the type of code that you really don't want to break in this way.

I object to the change.
Mike Stump June 23, 2010, 7:29 p.m. UTC | #28
On Jun 21, 2010, at 7:25 AM, Michael Matz wrote:
> On Mon, 21 Jun 2010, Nathan Sidwell wrote:
>>> Yes, namely that the author explicitely wrote a read access to vobj.
>>> To demonstrate, like
>>>   return vobj;
>>> implies a read from vobj, also
>>>   return vobj = x;
>>> implies a read from vobj.  The latter _also_ implies a store to vobj.  If
>>> you want the read to not happen the author better writes
>>>   vobj = x;
>>>   return x;
>> 
>> Then what about plain
>> 	vobj;
>> ?  Is that a read of vobj?
> 
> As said, implementation-defined I think.

:-(  The standard waffles around with the implementation defined terminology because they don't define wires and voltages and the CPU bus.  If they did, it would be plain as day what they mean.  They do this only because those accesses are beyond the capability of the standard to describe well and because the standards writers are lazy and/or not skilled enough to get the language correct,

The basics are pretty simple, consider a byte wide bus, single processor, no cache, with a char access.  A read access of the abstract machine is mapped to read bus cycle, and a write is mapped to a write cycle.  This isn't meant to be opaque or surprising.

The use terminology like:

  What constitutes an access to  an object  that  has volatile-qualified type is implementation-defined.

because exactly what happens when you access a character on a machine that doesn't have an 8-bit wide bus is slightly trickier to define.  This isn't meant to be used to arbitrarily change the mapping of the abstract machine onto the physical machine.  It is wrong to think one can use the latitude in the standard due to implementation defined bits to add or erase entire loads and stores.

For example, we could define all stores to volatile objects on x86_64 machines of width 64-bit to not happen, but that would be crazy, but the precise wording of the standard actually allows it.  If there were a mapping standard for C on x86_64 hardware that refined and defined the abstract semantics to the actual bus, you'd discover in that standard, there would be no wiggle room for this sort of declaration.

>  (I think we should define it to be no access, unlike 'x = vobj;' even if x is unused later.).

Again, it is wrong to abuse the latitude in the standard like this.  If the standard says there is a read access on the abstract machine, then there is a read access on the physical machine (at least when the sizes match).  We define an access, iff there is a access on the abstract machine.

>> what about hiding the assignment inside a cond expr
>>     cond ? vobj = expr : 0;
>> Is that a re-read of vobj?
> 
> If that's the full expression I think we should say that this doesn't 
> constitute a read access.

The standard describes all accesses on the abstract machine, the model we use for gcc is that every single access is mapped to a single hardware bus cycle, as long as the size of the access and the bus are the same.  For a size mismatch, I'd need more room, but the rules are equally as predicable.  A 64-bit store for example, on a 32-bit bus happens as two 32-bit cycles.  Give that mapping, we can judge the above as wrong.

I'm against the idea of using any other mapping.  So, I'd rather you argue the behavior of what gcc should do, but stating what the requite semantics of the abstract machine of the language standard mandates.  Then, what gcc should do is exactly match that.  I object to anything else.
Mark Mitchell June 24, 2010, 5:54 a.m. UTC | #29
Mike Stump wrote:

> If someone wanted to fix gcc, the proper way would be to get the C
> and C++ committees on the same page, nail it down, or make it a hard
> error, and remove any latitude and confusion and make them agree.

Are you saying that C and C++ require different behavior for:

  x = y = z;

where "y" is volatile?  Or are you saying that C++ requires that "y" be
read while C does not specify it one way or the other?

Please state your position more clearly.

> An assignment expression has the value of the left operand after the 
> assignment

I don't think this is nearly as clear as you do.  Furthermore, I think
existing practice from other embedded compilers is pretty important;
your statement that "the semantics in GCC has been the industry standard
for at least 15 years" is overly strong.  But, changing GCC's historic
behavior is clearly not desirable, and that's an argument against
eliminating the read.

However, I cannot see any good justification for:

  int x;
  volatile int y;
  int z;
  x ? y = z : 0

generating a read from "y" while:

  x ? y = 0 : 0

does not.

Which behavior are you claiming is correct here?
Nathan Sidwell June 24, 2010, 8:04 a.m. UTC | #30
On 06/23/10 19:36, Mike Stump wrote:


> C++ says:

C and C++ differ in their cast-to-void behaviour.  In particular, in C++
   (void)obj;
does not cause a read of obj, because no lvalue->rvalue decay occurs.  This is 
very confusing for volatiles and incompatible with established C semantics.  As 
such G++ will cause a read of a volatile scalar obj in these cases.  For a 
volatile aggregate obj, it will emit a warning.

In C++ the result of an assignment is an lvalue.  It is not in C.

> You can't just willy nilly change this:
>
> int i, j, k;
> void foo(int&j) {
>    foo(i = k);
> }
>
> can sense if you get it right or not.  Add a volatile on i, and you discover, you can't just change this as you see fit, in particular, not in the way you all seem to want, which is just plain wrong.

I do not know what you are saying here.  Making (just) i volatile will render 
your code example ill-formed.

> Further, C says:
>
>    An assignment expression has the value of the left operand after the assignment
>
> Now, this is pretty plain, the value is the left operand.  They choose this wording as they didn't want the return value to be a modifiable lvalue.  I concede there is enough wiggle room here to confuse users and give implementors enough latitude to try and claim they are following the standard, but really, the return value is the valuer of the left operand, if it weren't, the standard would not say it was.  It could have said, the value is the value that was stored in the left operand, or the value is the right operand after conversion to the type of the left operand, or a host of other possibilities.

Are you saying that in C, both

   (void)obj;
and
   (void)(obj = value);

should behave the same wrt the behaviour of reads from a volatile obj?  I 
thought there was consensus that they should not.

nathan
Michael Matz June 24, 2010, 11:01 a.m. UTC | #31
Hi,

On Wed, 23 Jun 2010, Mike Stump wrote:

> >  (I think we should define it to be no access, unlike 'x = vobj;' even if x is unused later.).
> 
> Again, it is wrong to abuse the latitude in the standard like this.  If 
> the standard says there is a read access on the abstract machine,

The point of this whole thread is that the standard doesn't say there's a 
read access for "vobj;" (full expression).  I tried to argue in this 
thread that there should be a read and a write access of vobj in
 "x = vobj = y;" .  From that position some surprising consequences would 
follow, which I tried to either argue irrelevant or wrongly implied.

I'm not sure what exactly your position on this whole matter is, except 
for "let's do exactly what the standard says".  I'm not at all disagreeing 
with the position, but this very thread shows quite clearly that there's 
disagreement about _what_ exactly the standard says.  So you should rather 
try to explain what you think the standard implies (I myself don't know if 
you're for or against a re-read).


Ciao,
Michael.
Mike Stump June 24, 2010, 2:24 p.m. UTC | #32
On Jun 23, 2010, at 10:54 PM, Mark Mitchell wrote:
> Mike Stump wrote:
>> If someone wanted to fix gcc, the proper way would be to get the C
>> and C++ committees on the same page, nail it down, or make it a hard
>> error, and remove any latitude and confusion and make them agree.
> 
> Are you saying that C and C++ require different behavior for:
> 
>  x = y = z;
> 
> where "y" is volatile?

In my book no, they do not.  I think a reasonable person can also say that they do, because the C standard can be read in a way that is difference from the C standard.  C++ requires a re-read of y.  For consistency, the C standard should be read in a way that requires the re-read of y.

> Or are you saying that C++ requires that "y" be read while C does not specify it one way or the other?

This is the essence of the second point above.

> Please state your position more clearly.

C++ requires a re-read of y, the patch was going to remove the re-read, I objected because the patch then makes the compiler not conform to the C++ standard.  The patch was going to make C differ from C++, should the semantic for C++ be put back to match the standard. I really don't like to see the behavior of C++ and C differ when they don't need to.

>> An assignment expression has the value of the left operand after the 
>> assignment
> 
> I don't think this is nearly as clear as you do.

Ok.  Let's talk about it.  I objected because people seems to be going in the wrong direction.  Maybe I just misunderstood the effect of the patch.  The patch removes the re-read of y, right?

> Furthermore, I think existing practice from other embedded compilers is pretty important;

But to the exclusion of all of gcc users and the c++ standard?

> However, I cannot see any good justification for:
> 
>  int x;
>  volatile int y;
>  int z;
>  x ? y = z : 0
> 
> generating a read from "y" while:
> 
>  x ? y = 0 : 0
> 
> does not.

I wasn't arguing this case.  I believe both standards have them behaving the same.
Mark Mitchell June 24, 2010, 2:32 p.m. UTC | #33
Mike Stump wrote:

>> x = y = z;
>> 
>> where "y" is volatile?

> C++ requires a re-read of y, the patch was going to remove the
> re-read, I objected because the patch then makes the compiler not
> conform to the C++ standard.

I think that you have to read rather a lot into the C++ standard to
arrive at that conclusion.  But, I suggest that you raise the issue on
the C++ reflector and report back.  I think that it would be very
helpful to hear what other implementors think.

But, this point may be moot; I don't know if the proposed patch changes
the behavior here or not.  Nathan, can you clarify that?

>> int x; volatile int y; int z; x ? y = z : 0
>> 
>> generating a read from "y" while:
>> 
>> x ? y = 0 : 0
>> 
>> does not.
> 
> I wasn't arguing this case.  I believe both standards have them
> behaving the same.

And which way is that?  Do you think that y needs to be read, or not?
Mike Stump June 24, 2010, 3:42 p.m. UTC | #34
On Jun 24, 2010, at 1:04 AM, Nathan Sidwell wrote:
> On 06/23/10 19:36, Mike Stump wrote:
>> C++ says:
> 
> C and C++ differ in their cast-to-void behaviour.  In particular, in C++
>  (void)obj;
> does not cause a read of obj, because no lvalue->rvalue decay occurs.  This is very confusing for volatiles and incompatible with established C semantics.  As such G++ will cause a read of a volatile scalar obj in these cases.

Yeah, this was one of the later changes that I groaned over when it went in, I wish the C and C++ standards people were on the same page.  If they are to differ, I would not be against just making the construct an error, to prohibit people from making any use of a semantic that isn't the same in both languages.  I know the status quo is to just conform to the C semantic, and I didn't argue or object to that; it's just an unfortunate position to be in.  However, since we went in that direction, I wish people would formulate a change for the C++ language consistent with that and get that in, say, in the name of C compatibility.

> In C++ the result of an assignment is an lvalue.  It is not in C.
> 
>> You can't just willy nilly change this:
>> 
>> int i, j, k;
>> void foo(int&j) {
>>   foo(i = k);
>> }
>> 
>> can sense if you get it right or not.  Add a volatile on i, and you discover, you can't just change this as you see fit, in particular, not in the way you all seem to want, which is just plain wrong.
> 
> I do not know what you are saying here.  Making (just) i volatile will render your code example ill-formed.

Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:

volatile int i, j, k;
volatile int vi;
void foo(volatile int& j) {
  foo(i = k);
}

here, we need to bind j to the address of i.  The second case is something like:

volatile int i, j, k;
volatile int vi;
void foo(int j) {
  foo(i = k);
}

where the value passed to foo is the value read from i, and i being volatile, would mean a read of i.

>> Further, C says:
>> 
>>   An assignment expression has the value of the left operand after the assignment
>> 
>> Now, this is pretty plain, the value is the left operand.  They choose this wording as they didn't want the return value to be a modifiable lvalue.  I concede there is enough wiggle room here to confuse users and give implementors enough latitude to try and claim they are following the standard, but really, the return value is the valuer of the left operand, if it weren't, the standard would not say it was.  It could have said, the value is the value that was stored in the left operand, or the value is the right operand after conversion to the type of the left operand, or a host of other possibilities.
> 
> Are you saying that in C, both
> 
>  (void)obj;
> and
>  (void)(obj = value);
> 
> should behave the same wrt the behaviour of reads from a volatile obj?  I thought there was consensus that they should not.

Technically, I was arguing against the dropping of the re-read of vobj in the  a = vobj = c; case.  I believe doing so would cause g++ to violate the C++ language standard.
Paul Koning June 24, 2010, 4:10 p.m. UTC | #35
> > On 06/23/10 19:36, Mike Stump wrote:
> >> C++ says:
> >
> > C and C++ differ in their cast-to-void behaviour.  In particular, in
> C++
> >  (void)obj;
> > does not cause a read of obj, because no lvalue->rvalue decay
occurs.
> This is very confusing for volatiles and incompatible with established
> C semantics.  As such G++ will cause a read of a volatile scalar obj
in
> these cases.
> 
> Yeah, this was one of the later changes that I groaned over when it
> went in, I wish the C and C++ standards people were on the same page.
> If they are to differ, I would not be against just making the
construct
> an error, to prohibit people from making any use of a semantic that
> isn't the same in both languages.  I know the status quo is to just
> conform to the C semantic, and I didn't argue or object to that; it's
> just an unfortunate position to be in.  However, since we went in that
> direction, I wish people would formulate a change for the C++ language
> consistent with that and get that in, say, in the name of C
> compatibility.

As I see it, for C++ to deviate from C in this respect is a C++ bug.  

Making such a construct an error in C++ programs is probably a bit
strong, but I would want to see it be a warning (on by default).

	paul
Mike Stump June 24, 2010, 6:38 p.m. UTC | #36
On Jun 24, 2010, at 7:32 AM, Mark Mitchell wrote:
> Mike Stump wrote:
> 
>>> x = y = z;
>>> 
>>> where "y" is volatile?
> 
>> C++ requires a re-read of y, the patch was going to remove the
>> re-read, I objected because the patch then makes the compiler not
>> conform to the C++ standard.
> 
> I think that you have to read rather a lot into the C++ standard to
> arrive at that conclusion.

I disagree.  It we meant to create a temporary for a = b = c, when a b and c are all class types, we would have listed 5.17 in 12.2.  Do you know of any C++ compilers that so create a temporary?  g++ certainly doesn't.  Now, compare 6.6.3.  It can create a temporary, and it does list 12.2, and is listed by 12.2.

Do you think:

	(0, a)

can create a temporary for a as well?  The standard uses similar wording there as well:

  The  type  and value of the result are the type and value of the right
  operand; the result is an lvalue if its right operand is.

I think what your missing is kinda basic, from 1.7:

  An object is created by  a  definition  (_basic.def_), by a new-expression (_expr.new_) or by the imple-
  mentation (_class.temporary_)  when  needed.

This is meant to describe when objects are created.  We don't have a definition, so basic.def doesn't apply.  We don't have a new, so expr.new doesn't apply, and class.temporary doesn't apply, so, there is in fact no object created.  If one had been created the standard would has said it.  I think your confusing the as-if rule, which says, you can create temporaries all you want, as long as you can't observe it.  This is different, we can observe it, so it can't be created.

If you want to claim an object is create, start from 1.7, and tell me which of basic.def, expr.new or class.temporary is used, and we'll work forward from there.  Now, how do I know we're dealing with an object, because of 3.10:

   An  lvalue refers to an object or function.

Now, how do I know we're dealing with an lvalue:

  the result is an lvalue

QED.  So, the standard is perfectly clear on this point, but I do admit, the wording could be improved in 5.17.

> But, I suggest that you raise the issue on the C++ reflector and report back.  I think that it would be very helpful to hear what other implementors think.

I'd rather just survey what their compilers do.  If they all agree with you, maybe indeed g++ is the odd man out.  I have a sneaky suspicion that none of them do.  When all compilers match, I don't usually waste the reflector's time on such trivial matters, though, would be good to reword the standard to say the result is the left hand side, so that people are less able to be confused by the wording.

> But, this point may be moot; I don't know if the proposed patch changes
> the behavior here or not.  Nathan, can you clarify that?

I thought his patch was clear:

+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_8" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_8," } } */
+  return cond ? obj_8 = 0 : 0;

>>> int x; volatile int y; int z; x ? y = z : 0
>>> 
>>> generating a read from "y" while:
>>> 
>>> x ? y = 0 : 0
>>> 
>>> does not.
>> 
>> I wasn't arguing this case.  I believe both standards have them
>> behaving the same.
> 
> And which way is that?  Do you think that y needs to be read, or not?

In:

volatile int i, j, k;
volatile int vi;
void foo(int p) {
  k = (i ? j=0 : k);
}

j is re-read.  It is re-read regardless if j=0, or j=k is used in the source.  Now, for the simpler:

volatile int i, j, k;
volatile int vi;
void foo(int p) {
  i ? j=0 : 0;
}

neither gcc nor g++ re-reads, and I'm not arguing changing that.  Nor is anyone suggesting changing that, so I don't see the point of asking.  I'm dodging what the standard says, well, unless someone wants to propose we change how they behave currently.
Nathan Sidwell June 25, 2010, 6:22 a.m. UTC | #37
On 06/24/10 15:32, Mark Mitchell wrote:
> Mike Stump wrote:
>
>>> x = y = z;
>>>
>>> where "y" is volatile?
>
>> C++ requires a re-read of y, the patch was going to remove the
>> re-read, I objected because the patch then makes the compiler not
>> conform to the C++ standard.
>
> I think that you have to read rather a lot into the C++ standard to
> arrive at that conclusion.  But, I suggest that you raise the issue on
> the C++ reflector and report back.  I think that it would be very
> helpful to hear what other implementors think.
>
> But, this point may be moot; I don't know if the proposed patch changes
> the behavior here or not.  Nathan, can you clarify that?

Yes, it changes the behaviour for C++ in the same way it changes it for C. 
Specifically the assigned-to object is not reread.

For C and C++ to have different behaviour wrt volatile accesses is a mistake 
(hence, as I mentioned, previous changes to G++ to make it have the same 
behaviour as gcc).

nathan
Nathan Sidwell June 25, 2010, 6:35 a.m. UTC | #38
On 06/24/10 16:42, Mike Stump wrote:

> Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:
>
> volatile int i, j, k;
> volatile int vi;
> void foo(volatile int&  j) {
>    foo(i = k);
> }
>
> here, we need to bind j to the address of i.  The second case is something like:

Correct.  The patch does not change this behaviour -- the C++ front end already 
has to explicitly break out the lvalue from the assignment to conform to the 
semantics of TREEs.  The current G++ behaviour is not to re-read the assigned-to 
value.

> volatile int i, j, k;
> volatile int vi;
> void foo(int j) {
>    foo(i = k);
> }
>
> where the value passed to foo is the value read from i, and i being volatile, would mean a read of i.

This is just the use-of-assignment value embedded in a different context.  Or do 
you consider it somewhat different from the
   non_vol = vol = expr;
case?

>>> Further, C says:
>>>
>>>    An assignment expression has the value of the left operand after the assignment
>>>
>>> Now, this is pretty plain, the value is the left operand.  They choose this wording as they didn't want the return value to be a modifiable lvalue.  I concede there is enough wiggle room here to confuse users and give implementors enough latitude to try and claim they are following the standard, but really, the return value is the valuer of the left operand, if it weren't, the standard would not say it was.  It could have said, the value is the value that was stored in the left operand, or the value is the right operand after conversion to the type of the left operand, or a host of other possibilities.
>>
>> Are you saying that in C, both
>>
>>   (void)obj;
>> and
>>   (void)(obj = value);
>>
>> should behave the same wrt the behaviour of reads from a volatile obj?  I thought there was consensus that they should not.
>
> Technically, I was arguing against the dropping of the re-read of vobj in the  a = vobj = c; case.  I believe doing so would cause g++ to violate the C++ 
language standard

You stated something about the C standard, my question was about the behaviour 
in C to clarify what you meant.  Do you have an opinion about that?

I am having a hard time following your reasoning, as it appears to switch 
between C and C++ standards.

nathan
Nathan Sidwell June 25, 2010, 6:50 a.m. UTC | #39
On 06/24/10 19:38, Mike Stump wrote:
> On Jun 24, 2010, at 7:32 AM, Mark Mitchell wrote:
>> Mike Stump wrote:
>>
>>>> x = y = z;
>>>>
>>>> where "y" is volatile?
>>
>>> C++ requires a re-read of y, the patch was going to remove the
>>> re-read, I objected because the patch then makes the compiler not
>>> conform to the C++ standard.
>>
>> I think that you have to read rather a lot into the C++ standard to
>> arrive at that conclusion.
>
> I disagree.  It we meant to create a temporary for a = b = c, when a b and c are all class types, we would have listed 5.17 in 12.2.  Do you know of any C++ compilers that so create a temporary?  g++ certainly doesn't.  Now, compare 6.6.3.  It can create a temporary, and it does list 12.2, and is listed by 12.2.

We were talking about scalars.  volatile structs are treated differently.  As I 
already mentioned, in G++, '(void)volatile_class_object' does not cause 
lvalue->rvalue  decay and does not cause a read of all the members of the 
object.  I think the behaviour of volatile structs in C++ is too ill-specified 
to meaningfully talk about when accesses to the members occur.

I think the goal should be to make accesses to volatile scalars, as used when 
poking at hardware, follow simple, composable, consistent rules.  IMHO the 
current implemented behaviour is none of those.

> j is re-read.  It is re-read regardless if j=0, or j=k is used in the source.  Now, for the simpler:
>
> volatile int i, j, k;
> volatile int vi;
> void foo(int p) {
>    i ? j=0 : 0;
> }
>
> neither gcc nor g++ re-reads, and I'm not arguing changing that.  Nor is anyone suggesting changing that, so I don't see the point of asking.  I'm dodging what the standard says, well, unless someone wants to propose we change how they behave currently.

But gcc does reread when assigning a non-constant value.  As I pointed out, 
GCC's behaviour is inconsistent.

nathan.
Mike Stump June 25, 2010, 4:41 p.m. UTC | #40
On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
> On 06/24/10 16:42, Mike Stump wrote:
> 
>> Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:
>> 
>> volatile int i, j, k;
>> volatile int vi;
>> void foo(volatile int&  j) {
>>   foo(i = k);
>> }
>> 
>> here, we need to bind j to the address of i.  The second case is something like:
> 
> Correct.  The patch does not change this behaviour -- the C++ front end already has to explicitly break out the lvalue from the assignment to conform to the semantics of TREEs.  The current G++ behaviour is not to re-read the assigned-to value.

g++ seems not to share your position:

	movl	_i, %eax
	movl	%eax, (%esp)

volatile int i, j, k;
volatile int vi;
void foo(int j) {
  foo(i = k);
}

GNU C++ (GCC) version 4.6.0 20100620 (experimental) [trunk revision 161045] (x86_64-apple-darwin10)
	compiled by GNU C version 4.2.1 (Apple Inc. build 5659), GMP version 5.0.1

?  Certainly I must misunderstand something, can you spot it?


> This is just the use-of-assignment value embedded in a different context.  Or do you consider it somewhat different from the
>  non_vol = vol = expr;
> case?

I don't believe that standard distinguishes.

> 
>>>> Further, C says:
>>>> 
>>>>   An assignment expression has the value of the left operand after the assignment
>>>> 
>>>> Now, this is pretty plain, the value is the left operand.  They choose this wording as they didn't want the return value to be a modifiable lvalue.  I concede there is enough wiggle room here to confuse users and give implementors enough latitude to try and claim they are following the standard, but really, the return value is the valuer of the left operand, if it weren't, the standard would not say it was.  It could have said, the value is the value that was stored in the left operand, or the value is the right operand after conversion to the type of the left operand, or a host of other possibilities.
>>> 
>>> Are you saying that in C, both
>>> 
>>>  (void)obj;
>>> and
>>>  (void)(obj = value);
>>> 
>>> should behave the same wrt the behaviour of reads from a volatile obj?  I thought there was consensus that they should not.
>> 
>> Technically, I was arguing against the dropping of the re-read of vobj in the  a = vobj = c; case.  I believe doing so would cause g++ to violate the C++ 
> language standard
> 
> You stated something about the C standard, my question was about the behaviour in C to clarify what you meant.  Do you have an opinion about that?

The standard has an opinion I respect:

  An assignment expression has the value of the left operand

value in this context means:

       3.15
       [#1] object
       region of data storage in  the  execution  environment,  the
       contents of which can represent values                       |

and the left operand means vobj (in your snippet).

C is the same.  C++ interpreted what the C standard meant at time and tried to clarify.  There is a slight mod in this area due to C++, which is why the language is slightly different, but the intent of the C++ committee was to make them match C.

The standards have many tricky areas, many hard to read things, but when the chain of reasoning spans just 2 sections, and uses 1 or two verbs each, using simple, well defined terms, I just don't see the room to be confused.

> I am having a hard time following your reasoning, as it appears to switch between C and C++ standards.

I don't switch between them, I've expounded on both, because others brought both up.  In this case, one can be used to understand the meaning of the other, as both are intended to match (save for the very obvious lvalue result), that was the one addition the C++ people did to better match the requirements for C++.  C has no need to adopt that, but since the pulled in modifiable lvalue, they could pull in this change safely, if they wanted.  Previously, they didn't have the terminology to do it, now they do.
Mike Stump June 25, 2010, 6:14 p.m. UTC | #41
On Jun 24, 2010, at 11:50 PM, Nathan Sidwell wrote:
> On 06/24/10 19:38, Mike Stump wrote:
>> On Jun 24, 2010, at 7:32 AM, Mark Mitchell wrote:
>>> Mike Stump wrote:
>>> 
>>>>> x = y = z;
>>>>> 
>>>>> where "y" is volatile?
>>> 
>>>> C++ requires a re-read of y, the patch was going to remove the
>>>> re-read, I objected because the patch then makes the compiler not
>>>> conform to the C++ standard.
>>> 
>>> I think that you have to read rather a lot into the C++ standard to
>>> arrive at that conclusion.
>> 
>> I disagree.  It we meant to create a temporary for a = b = c, when a b and c are all class types, we would have listed 5.17 in 12.2.  Do you know of any C++ compilers that so create a temporary?  g++ certainly doesn't.  Now, compare 6.6.3.  It can create a temporary, and it does list 12.2, and is listed by 12.2.
> 
> We were talking about scalars.

I don't find any limitation in 5.17 to scalars or to objects of class type.  If you examine the wording in 8.5.3 for example, a place that we all know does create temporaries:

    --Otherwise, a temporary of type "cv1 T1" is created and initialized
      from  the  initializer expression using the rules for a non-refer-
      ence copy initialization  (_dcl.init_).   The  reference  is  then
      bound  to  the  temporary.

we can see the usual language the standard uses to create temporaries.  It _must_ do this, so that the results of == on &lval can be defined.  We use 12.2 as a proxy to find places in the standard that create temporaries.  One can also just search the standard for the word temporary, if one wanted to.  Since 5.17 doesn't distinguish between class nor scalars, we are free to use the semantics of class types to better understand the semantics for scalars.  These semantics _must_ be the same, because there is no stated difference.  The standard is not meant to be misread.  It isn't formal enough to withstand people intentionally or otherwise, misreading it.

So, what we are talking about are the semantics of 5.17, and those are not limited to scalars.

> volatile structs are treated differently.

In a = vob = c; stucts are treated the same.  This is the case we were discussing.  We can know that, because it appears on the 6th lines of this email.

>  I think the behaviour of volatile structs in C++ is too ill-specified to meaningfully talk about when accesses to the members occur.

Again, I disagree.  We could embark upon what the standard says, but, let's just agree to not, unless someone wants to learn what it says, or someone wants to change the status quo behavior in a way that deviates from the standard.

> I think the goal should be to make accesses to volatile scalars, as used when poking at hardware, follow simple, composable, consistent rules.

Yeah, the problem with that is implicit in that, is, I want to make the compiler agree to the ruleset I just invented because I like it, and if we do that, the semantics then change day by day, by the whim of the person doing the patch today, and the each implementor gets to choose a different rule, and the users don't benefit.  The other possibility is to make it conform to the language standard, when reasonable.  I think the later is the best option.  No one has argued that they understand what the standard says and want to intentionally deviate from it, nor have they stated a good reason for doing that.

So, could you please restate what you want to do.  Do you want gcc to follow the language standard, or, would you rather ignore the standard and just invent some rules on the fly for gcc to follow?

>  IMHO the current implemented behaviour is none of those.

Concerning:

Sometimes we re-read the assigned-to object, and sometimes we do not.  For instance,
  return vobj = data;
will cause a reread of vobj, IF data is not a constant.

the proper solution is to make it always reread vobj.  Simple and consistent, while matching the standard, no?

This sort of bug happens because people forget the, this optimization isn't valid when TREE_THIS_VOLATILE is set.  The solution is to add the missing check.  We know it is the optimizer, as if you replace the constant with anything but a constant, we get the proper semantics.  That's proof positive that the bug lies no in the semantics of all other cases, but rather the semantics of the constant case.

>> j is re-read.  It is re-read regardless if j=0, or j=k is used in the source.  Now, for the simpler:
>> 
>> volatile int i, j, k;
>> volatile int vi;
>> void foo(int p) {
>>   i ? j=0 : 0;
>> }
>> 
>> neither gcc nor g++ re-reads, and I'm not arguing changing that.  Nor is anyone suggesting changing that, so I don't see the point of asking.  I'm dodging what the standard says, well, unless someone wants to propose we change how they behave currently.
> 
> But gcc does reread when assigning a non-constant value.  As I pointed out, GCC's behaviour is inconsistent.

Ah, I want hoping to avoid that case...  Oh well...  [ pause ]

g++ doesn't reread in that case.  :-)  I think that matches the language standard 5.16:

4 If  the  second and third operands are lvalues and have the same type,
  the result is of that type and is an lvalue.

and no decay, no access.  I don't see that as problematic.  gcc does access, and I think that follows the C standard.  When exploring it, we find only constants screw it up, so the bug again, is in the optimizer, and the code missing is the one that says, don't do this optimization when volatile.  When that bug is fixed, gcc (c language) is then consistent with itself.

As for the inconsistency between C and C++.  I don't feel as strongly on how to resolve this.  I might vote to leave the g++ hack out here, but if people wanted the hack to be all lvalues cast to void are converted to rvalues, I don't feel strongly enough to oppose, though, I wish people would rather hit up the C and C++ people, lock them in a room, and say you can't come out until one of you relents.  I'd also be ok with it being a hard error, as this can be refined later when the standards people agree on a consistent semantic.  Hard error (off with pedantic) or a note might  be my preferred solution.
Mark Mitchell June 25, 2010, 6:44 p.m. UTC | #42
Mike Stump wrote:

> So, could you please restate what you want to do.  Do you want gcc to
> follow the language standard, or, would you rather ignore the
> standard and just invent some rules on the fly for gcc to follow?

There's no need to take that tone.  Nathan and I have both participated
in standards committees and in making G++ much more conformant to the
standard.  Of course we don't want to make things up on the fly.

But, you are asserting that the standard unambiguously mandates a
particular behavior.  I, at least, don't think that's clear.  I also
don't think that in this area the standard is more important than
existing practice, both in GCC and in other compilers.

Rather than all this discussion of the standard, I'd like to see a list
of the cases that are controversial, together with a table showing what
compilers do reads and which do not.

Nathan, perhaps you can put together the table?  And then we can ask
volunteers to start filling it in?

Thanks,
Mike Stump June 26, 2010, 3:24 a.m. UTC | #43
On Jun 25, 2010, at 11:44 AM, Mark Mitchell wrote:
> There's no need to take that tone.

Sorry for the tone.  I honestly don't know if people want to deviate from the standard, and if so, why, or if they just have a different idea of what it says, or why they think it says what they think.

> Of course we don't want to make things up on the fly.

Very little of the preceding has any backing from the language of the standard for the proposed behavior change, hence my confusion.

> But, you are asserting that the standard unambiguously mandates a particular behavior.

Yes,

> I, at least, don't think that's clear.

I'd like to understand how you interpret the standard.  If you could point out the support in the standard for a copy to be made, that would help me understand better.

Other compilers might differ on the re-read.  Even if they did, I'd still object to changing gcc to match them.  I'd rather talk with their implementors and figure out if they think they conform to the standard or not, and if not, why?  Could be just a simple bug in their compiler that they would fix.
Mark Mitchell June 26, 2010, 3:46 a.m. UTC | #44
Mike Stump wrote:

> I'd like to understand how you interpret the standard.

I simply think it does not say.  I don't think it says make a copy.  I
don't think it says don't make a copy.  I understand how you are reading
it, but I think you're finding meaning where none is necessarily
present.  I'll be persuaded if you show me a consensus of people on the
standards reflector, but you didn't want to ask on the reflector.

If it is unspecified, I'd rather not do the read.  I don't argue the
standard mandates not doing the read, but I feel that it's better
behavior.  I think it's odd to do a read from "y" in "x = y = z", but I
think it's *very* odd to do a read from "y" in the case of "x ? y = z :
...", which is what Nathan says we presently do.

> Other compilers might differ on the re-read.  Even if they did, I'd
> still object to changing gcc to match them.  I'd rather talk with
> their implementors and figure out if they think they conform to the
> standard or not, and if not, why?  Could be just a simple bug in
> their compiler that they would fix.

I don't find that likely.  Changing this behavior will change the
meaning of existing programs in mysterious ways.  I doubt you'll find
compiler implementors eager to do that.  But, we can't know until we
test the compilers.  Maybe they all agree with you.

Backwards compatibility is also the best reason not to change GCC; even
if the standard unambiguously mandated that we change, it might not make
sense to change, since it might break code that's worked with GCC for a
long time.

But, that doesn't resolve the constant vs. non-constant question.
That's an inconsistency so weird and horrid that I think we have to
change either the constant case or the non-constant case.  If we don't
make those cases consistent, we'll find that optimization levels affect
whether a volatile location is read and that's very bad indeed.  So, I
think that GCC is going to have to change it's behavior in one or the
other of these cases.

Right now you're asserting that the standard mandates a particular
behavior, but you seem unwilling to back that up by getting a consensus
on the reflector, and you're saying that existing practice by other
compilers won't change your opinion.  That seems a pretty rigid
position.  I'd be a lot more persuaded if you helped to establish either
a consensus on the standards reflector or demonstrated a consensus of
other compilers.
Richard Kenner June 26, 2010, 4:09 a.m. UTC | #45
> I honestly don't know if people want to deviate from the standard, and if
> so, why, or if they just have a different idea of what it says, or why
> they think it says what they think.

Implementing a standard precisely is good.  Doing what users expect is
also good.  When those two conflict, we have to choose which to do.
The former is USUALLY the right choice, but not always.  It depends on
the level of ambiguity in the standard and the historical legimacy of
the expectation.
Richard Biener June 26, 2010, 9:35 a.m. UTC | #46
On Sat, Jun 26, 2010 at 5:46 AM, Mark Mitchell <mark@codesourcery.com> wrote:
> Mike Stump wrote:
>
>> I'd like to understand how you interpret the standard.
>
> I simply think it does not say.  I don't think it says make a copy.  I
> don't think it says don't make a copy.  I understand how you are reading
> it, but I think you're finding meaning where none is necessarily
> present.  I'll be persuaded if you show me a consensus of people on the
> standards reflector, but you didn't want to ask on the reflector.
>
> If it is unspecified, I'd rather not do the read.  I don't argue the
> standard mandates not doing the read, but I feel that it's better
> behavior.  I think it's odd to do a read from "y" in "x = y = z", but I
> think it's *very* odd to do a read from "y" in the case of "x ? y = z :
> ...", which is what Nathan says we presently do.
>
>> Other compilers might differ on the re-read.  Even if they did, I'd
>> still object to changing gcc to match them.  I'd rather talk with
>> their implementors and figure out if they think they conform to the
>> standard or not, and if not, why?  Could be just a simple bug in
>> their compiler that they would fix.
>
> I don't find that likely.  Changing this behavior will change the
> meaning of existing programs in mysterious ways.  I doubt you'll find
> compiler implementors eager to do that.  But, we can't know until we
> test the compilers.  Maybe they all agree with you.
>
> Backwards compatibility is also the best reason not to change GCC; even
> if the standard unambiguously mandated that we change, it might not make
> sense to change, since it might break code that's worked with GCC for a
> long time.

What did GCC do for all these cases pre-tree-ssa?

Richard.
Michael Matz June 26, 2010, 5:30 p.m. UTC | #47
Hi,

On Sat, 26 Jun 2010, Richard Guenther wrote:

> > Backwards compatibility is also the best reason not to change GCC; 
> > even if the standard unambiguously mandated that we change, it might 
> > not make sense to change, since it might break code that's worked with 
> > GCC for a long time.
> 
> What did GCC do for all these cases pre-tree-ssa?

Trying 3.3, and this testcase:

-----------------------
volatile int vobj1, vobj2, vobj3, vobj4, vobj5, vobj6, vobj7, vobj8, vobj9;
int x, y, z;
void f1 (int i)
{
  vobj1 = y;
  x = vobj2 = y;
  x ? vobj3 = y : z;
  x = y ? vobj4 = z : i;
  vobj5;
  vobj6 = 42;
  x = vobj7 = 43;
  x ? vobj8 = 44 : z;
  x = y ? vobj9 = 45 : i;
}
-----------------------

I.e. testing all cases we were discussing in this thread, constant and 
non-constant RHS.  cc1 and cc1plus generate the same accesses, in addition 
optimization levels make no difference (as expected).  The relevant 
instructions are this in all cases:

% grep 'mov.*vobj' volatile-check.s
        movl    %edx, vobj1
        movl    %edx, vobj2
        movl    vobj2, %eax
        movl    %edx, vobj3
        movl    %eax, vobj4
        movl    vobj4, %eax
        movl    vobj5, %eax
        movl    %eax, vobj6
        movl    %eax, vobj7
        movl    vobj7, %eax
        movl    %ecx, vobj8
        movl    %eax, vobj9

I.e. a (re-)read occurs for vobj2, vobj4, vobj5 and vobj7, that is these 
cases:

  x = vobj2 = y;
  x = y ? vobj4 = z : i;
  vobj5;
  x = vobj7 = 43;

Interestingly a re-read doesn't occur for vobj9:
  x = y ? vobj9 = 45 : i;
that is the same case as vobj4, just with a constant RHS.  I'd consider 
this a bug.  (For completeness, the reread of vobj4 of course only happens 
conditionally)

FWIW, our current trunk in addition generates re-reads for vobj3, vobj8 
and vobj9.  If one were to follow my (and Mikes?) reading of the standard 
rereading vobj3 and vobj8 would be wrong.

All of these compilers agree that a read is to be emitted for a bare vobj 
access in void context: "vobj5;" but not for the simple assignment
"vobj1 = x;".

ICC 11.0 and 11.1 only generate a read of vobj5 (i.e. no re-reading occurs 
at all) for the C language, and no read at all for the C++ language.


Ciao,
Michael.
Mark Mitchell June 26, 2010, 5:35 p.m. UTC | #48
Michael Matz wrote:

> Trying 3.3, and this testcase:

Thanks for doing this research!

Would you be willing to make a Wiki page with this information on it?

I think if we can fill in a table showing which compilers do what, that
would be extremely helpful.  If we had each of the examples you
collected in rows, and columns showing whether each compiler reads the
volatile variable, we could see just how much consensus there is.

> ICC 11.0 and 11.1 only generate a read of vobj5 (i.e. no re-reading occurs 
> at all) for the C language, and no read at all for the C++ language.

That's interesting.  Of course, ICC is not terribly heavily used as an
embedded compiler, which is where there is the most use of volatile.
(Of course, volatile is used in multi-threaded non-embedded programs as
well, though there the exact number of reads/writes is probably less
important to many users.)

Thanks,
Michael Matz June 26, 2010, 6:23 p.m. UTC | #49
Hi,

On Sat, 26 Jun 2010, Mark Mitchell wrote:

> Would you be willing to make a Wiki page with this information on it?

http://gcc.gnu.org/wiki/VolatileAccessComparison


Ciao,
Michael.
Mark Mitchell June 26, 2010, 7:57 p.m. UTC | #50
Michael Matz wrote:

>> Would you be willing to make a Wiki page with this information on it?
> 
> http://gcc.gnu.org/wiki/VolatileAccessComparison

Thanks!  This is a big help.

I filled in rows for RealView 3.1 and RealView 4.0 and for Visual C++
2010.  It looks like RealView behaves like ICC, which isn't too
surprising given that they are both EDG-based compilers.  On the other
Visual C++ behaves like the current 4.6 trunk.  So, we certainly do not
have agreement across implementors as to what behavior is right.

I'd like to see what some of the other embedded compilers like Green
Hills, CodeWarrior, IAR, and Keil do.

One thing that I'm confused about is that Nathan reported inconsistency
between the constant and non-constant cases.  But, while your table
shows that for GCC 3.3, it doesn't show it for the current 4.6 trunk.
Nathan, are we missing something in the test cases relative to the
inconsistency you reported?

Thanks,
Nathan Sidwell June 28, 2010, 6:28 a.m. UTC | #51
On 06/25/10 17:41, Mike Stump wrote:
> On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
>> On 06/24/10 16:42, Mike Stump wrote:
>>
>>> Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:
>>>
>>> volatile int i, j, k;
>>> volatile int vi;
>>> void foo(volatile int&   j) {
>>>    foo(i = k);
>>> }
>>>
>>> here, we need to bind j to the address of i.  The second case is something like:
>>
>> Correct.  The patch does not change this behaviour -- the C++ front end already has to explicitly break out the lvalue from the assignment to conform to the semantics of TREEs.  The current G++ behaviour is not to re-read the assigned-to value.
>
> g++ seems not to share your position:
>
> 	movl	_i, %eax
> 	movl	%eax, (%esp)
>
> volatile int i, j, k;
> volatile int vi;
> void foo(int j) {
>    foo(i = k);
> }
>
> GNU C++ (GCC) version 4.6.0 20100620 (experimental) [trunk revision 161045] (x86_64-apple-darwin10)
> 	compiled by GNU C version 4.2.1 (Apple Inc. build 5659), GMP version 5.0.1
>
> ?  Certainly I must misunderstand something, can you spot it?

You have changed the testcase to be passing by value from passing by reference. 
  Why do you think those would be equivalent?

nathan
Nathan Sidwell June 28, 2010, 7:06 a.m. UTC | #52
On 06/26/10 20:57, Mark Mitchell wrote:

> One thing that I'm confused about is that Nathan reported inconsistency
> between the constant and non-constant cases.  But, while your table
> shows that for GCC 3.3, it doesn't show it for the current 4.6 trunk.
> Nathan, are we missing something in the test cases relative to the
> inconsistency you reported?

hm, I have just tested mainling, and I see the same as Michael -- namely a 
reread for constant assignments.   GCC 4.4.3 is different though -- it does not 
do the reread.

I'll add another row to the wiki, with 4.4.3's behaviour.

nathan
Nathan Sidwell June 28, 2010, 7:09 a.m. UTC | #53
On 06/28/10 08:06, Nathan Sidwell wrote:
> On 06/26/10 20:57, Mark Mitchell wrote:
>
>> One thing that I'm confused about is that Nathan reported inconsistency
>> between the constant and non-constant cases. But, while your table
>> shows that for GCC 3.3, it doesn't show it for the current 4.6 trunk.
>> Nathan, are we missing something in the test cases relative to the
>> inconsistency you reported?
>
> hm, I have just tested mainling, and I see the same as Michael -- namely
> a reread for constant assignments. GCC 4.4.3 is different though -- it
> does not do the reread.

sigh, I'd got .i and .ii suffixes mixed up.  it appears GCC 4.4.3 has different 
behaviour for C and C++.  I'll be really careful filling in the table.

nathan
Nathan Sidwell June 28, 2010, 7:44 a.m. UTC | #54
On 06/25/10 19:14, Mike Stump wrote:

> I don't find any limitation in 5.17 to scalars or to objects of class type.  If you examine the wording in 8.5.3 for example, a place that we all know does create temporaries:

I did not make the claim that the standard distinguished.  I am make the claim 
that we want simple, consistent and composable rules for accessing scalar device 
registers.

As you brought it up, I stated what I thought of volatile structure semantics.

> In a = vob = c; stucts are treated the same.  This is the case we were discussing.  We can know that, because it appears on the 6th lines of this email.

The case of structs is what you have brought to the mix.  The original 
discussion was about scalars.

>>   I think the behaviour of volatile structs in C++ is too ill-specified to meaningfully talk about when accesses to the members occur.
>
> Again, I disagree.  We could embark upon what the standard says, but, let's just agree to not, unless someone wants to learn what it says, or someone wants to change the status quo behavior in a way that deviates from the standard.

If you do not want to discuss what the standard says, why did you bring it up? 
This whole discussion is about discrepancies within the standards and between 
them and desired semantics.

>> I think the goal should be to make accesses to volatile scalars, as used when poking at hardware, follow simple, composable, consistent rules.
>
> Yeah, the problem with that is implicit in that, is, I want to make the compiler agree to the ruleset I just invented because I like it, and if we do that, the semantics then change day by day, by the whim of the person doing the patch today, and the each implementor gets to choose a different rule, and the users don't benefit.  The other possibility is to make it conform to the language standard, when reasonable.  I think the later is the best option.  No one has argued that they understand what the standard says and want to intentionally deviate from it, nor have they stated a good reason for doing that.

Could you rephrase that please, I cannot understand what you are claiming.

> So, could you please restate what you want to do.  Do you want gcc to follow the language standard, or, would you rather ignore the standard and just invent some rules on the fly for gcc to follow?

I claim the C and C++ standards are both not clear and at odds semantics that 
are necessary for hardware programming.

>>   IMHO the current implemented behaviour is none of those.
>
> Concerning:
>
> Sometimes we re-read the assigned-to object, and sometimes we do not.  For instance,
>    return vobj = data;
> will cause a reread of vobj, IF data is not a constant.
>
> the proper solution is to make it always reread vobj.  Simple and consistent, while matching the standard, no?

did you not read my original problem statement?  You seem to be ignoring 
associated issues that come with that interpretation, which I mentioned.  Could 
you clarify what you also claim the following expression-statements should reread?

vobj;
vobj = data;
expr, vobj = data;

They all have values.

nathan
Mike Stump June 30, 2010, 8:56 p.m. UTC | #55
On Jun 28, 2010, at 12:44 AM, Nathan Sidwell wrote:
> did you not read my original problem statement?

I did read it.

> You seem to be ignoring associated issues that come with that interpretation, which I mentioned.

I'm aware of them.  We'd all benefit if the C and C++ committees refined the language to make things more clear and to match each other.

> Could you clarify what you also claim the following expression-statements should reread?

Below is my take on what we should do.

> vobj;

read.

> vobj = data;

no re-read

> expr, vobj = data;

no re-read.
Mike Stump July 1, 2010, 1:02 a.m. UTC | #56
On Jun 27, 2010, at 11:28 PM, Nathan Sidwell wrote:
> On 06/25/10 17:41, Mike Stump wrote:
>> On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
>>> On 06/24/10 16:42, Mike Stump wrote:
>>> 
>>>> Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:
>>>> 
>>>> volatile int i, j, k;
>>>> volatile int vi;
>>>> void foo(volatile int&   j) {
>>>>   foo(i = k);
>>>> }
>>>> 
>>>> here, we need to bind j to the address of i.  The second case is something like:
>>> 
>>> Correct.  The patch does not change this behaviour -- the C++ front end already has to explicitly break out the lvalue from the assignment to conform to the semantics of TREEs.  The current G++ behaviour is not to re-read the assigned-to value.
>> 
>> g++ seems not to share your position:
>> 
>> 	movl	_i, %eax
>> 	movl	%eax, (%esp)
>> 
>> volatile int i, j, k;
>> volatile int vi;
>> void foo(int j) {
>>   foo(i = k);
>> }
>> 
>> GNU C++ (GCC) version 4.6.0 20100620 (experimental) [trunk revision 161045] (x86_64-apple-darwin10)
>> 	compiled by GNU C version 4.2.1 (Apple Inc. build 5659), GMP version 5.0.1
>> 
>> ?  Certainly I must misunderstand something, can you spot it?
> 
> You have changed the testcase to be passing by value from passing by reference.  Why do you think those would be equivalent?

The read happens when the the object referred to by the reference is accessed:

volatile int i, j;
int x, data, expr, k;

static inline void foo(volatile int &r) {
  int fetch = r;
}

int main() {
  foo(i=k);
}

	movl	_k, %eax
	movl	%eax, _i
	movl	_i, %eax

here, i is re-read.  Here, g++ in fact does re-read the assigned to value.
Nathan Sidwell July 5, 2010, 8:59 a.m. UTC | #57
On 06/30/10 21:56, Mike Stump wrote:

> Below is my take on what we should do.
>
>> vobj;
>
> read.
>
>> vobj = data;
>
> no re-read
>
>> expr, vobj = data;
>
> no re-read.

Ok, I think everyone's agreed about that.

Do you have a reference to the C standard that clarifies the difference?  Wrt 
the std, all those expression-statements have values - the first is an lvalue, 
the other 2 are rvalues.

Another example I missed is:
   expr, vobj;

I think that should read vobj.  But AFAICT 'expr, vobj = data;' and 'expr, 
vobj;' are identically rvalued according to the C std.  Is there anything in the 
std that justifies them behaving differently?

nathan
Nathan Sidwell July 5, 2010, 9:02 a.m. UTC | #58
On 07/01/10 02:02, Mike Stump wrote:
> On Jun 27, 2010, at 11:28 PM, Nathan Sidwell wrote:
>> On 06/25/10 17:41, Mike Stump wrote:
>>> On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
>>>> On 06/24/10 16:42, Mike Stump wrote:
>>>>
>>>>> Sorry for not expounding.  I thought it would be obvious I was talking about well formed code...  First case would be something like:
>>>>>
>>>>> volatile int i, j, k;
>>>>> volatile int vi;
>>>>> void foo(volatile int&    j) {
>>>>>    foo(i = k);
>>>>> }
>>>>>
>>>>> here, we need to bind j to the address of i.  The second case is something like:
>>>>
>>>> Correct.  The patch does not change this behaviour -- the C++ front end already has to explicitly break out the lvalue from the assignment to conform to the semantics of TREEs.  The current G++ behaviour is not to re-read the assigned-to value.
>>>
>>> g++ seems not to share your position:
>>>
>>> 	movl	_i, %eax
>>> 	movl	%eax, (%esp)
>>>
>>> volatile int i, j, k;
>>> volatile int vi;
>>> void foo(int j) {
>>>    foo(i = k);
>>> }
>>>
>>> GNU C++ (GCC) version 4.6.0 20100620 (experimental) [trunk revision 161045] (x86_64-apple-darwin10)
>>> 	compiled by GNU C version 4.2.1 (Apple Inc. build 5659), GMP version 5.0.1
>>>
>>> ?  Certainly I must misunderstand something, can you spot it?
>>
>> You have changed the testcase to be passing by value from passing by reference.  Why do you think those would be equivalent?
>
> The read happens when the the object referred to by the reference is accessed:

You're now changing the example again.  This time with a reference to volatile 
object.

>
> volatile int i, j;
> int x, data, expr, k;
>
> static inline void foo(volatile int&r) {
>    int fetch = r;
> }
>
> int main() {
>    foo(i=k);
> }
>
> 	movl	_k, %eax
> 	movl	%eax, _i
> 	movl	_i, %eax
>
> here, i is re-read.  Here, g++ in fact does re-read the assigned to value.

Yes I would expect this -- there is a sequence point between the assignment to i 
in the argument list, and the read of r in the inlined function.  I do not 
understand the point you are trying to make.

nathan
Mike Stump July 9, 2010, 5:14 a.m. UTC | #59
On Jul 5, 2010, at 2:02 AM, Nathan Sidwell wrote:
> On 07/01/10 02:02, Mike Stump wrote:
>> On Jun 27, 2010, at 11:28 PM, Nathan Sidwell wrote:
>>> On 06/25/10 17:41, Mike Stump wrote:
>>>> On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
>>>>> The current G++ behaviour is not to re-read the assigned-to value.
> 
>> Here, g++ in fact does re-read the assigned to value.
> 
> Yes I would expect this


Above you say that it doesn't re-read, then you agree with me that it does re-read...  :-)

Anyway, we're on the same page now with respect to this part of the subthread.  I suppose that if we defined re-read to be the process by which the value when used is re-fetched from memory instead of a cached value of the rhs then with that definition gcc always (barring stupid bugs under discussion) re-reads and never doesn't (or at least, if one agreed with me that the result is the value of the lhs).  For those that go with, the value is the converted value of the rhs, then we'd say that gcc never re-reads.
Mike Stump July 9, 2010, 5:27 a.m. UTC | #60
On Jul 5, 2010, at 1:59 AM, Nathan Sidwell wrote:
> Do you have a reference to the C standard that clarifies the difference?

Sure, it would be the part where the edits are when we ask them to regularize the rules with C++ after C++ fixes what they broke.  :-)  That is why I carefully chose the phrase `what we should do' as opposed to, what the standard says...

> Wrt the std, all those expression-statements have values - the first is an lvalue, the other 2 are rvalues.

Yeah, I know.

> Another example I missed is:
>  expr, vobj;
> 
> I think that should read vobj.

:-)  gcc fetches.  I'd not oppose that.

> But AFAICT 'expr, vobj = data;' and 'expr, vobj;' are identically rvalued according to the C std.  Is there anything in the std that justifies them behaving differently?

Nope, don't think so, at least in the C standard.
Nathan Sidwell July 9, 2010, 7:20 a.m. UTC | #61
On 07/09/10 06:14, Mike Stump wrote:
> On Jul 5, 2010, at 2:02 AM, Nathan Sidwell wrote:
>> On 07/01/10 02:02, Mike Stump wrote:
>>> On Jun 27, 2010, at 11:28 PM, Nathan Sidwell wrote:
>>>> On 06/25/10 17:41, Mike Stump wrote:
>>>>> On Jun 24, 2010, at 11:35 PM, Nathan Sidwell wrote:
>>>>>> The current G++ behaviour is not to re-read the assigned-to value.
>>
>>> Here, g++ in fact does re-read the assigned to value.
>>
>> Yes I would expect this
>
>
> Above you say that it doesn't re-read, then you agree with me that it does re-read...  :-)

As I said, the two examples are different.  Your modified example inserts a 
sequence point (but you've snipped my sentence explaining that).

> Anyway, we're on the same page now with respect to this part of the subthread.  I suppose that if we defined re-read to be the process by which the value when used is re-fetched from memory instead of a cached value of the rhs then with that definition gcc always (barring stupid bugs under discussion) re-reads and never doesn't (or at least, if one agreed with me that the result is the value of the lhs).  For those that go with, the value is the converted value of the rhs, then we'd say that gcc never re-reads.

No, we are not.

nathan
Nathan Sidwell July 9, 2010, 7:21 a.m. UTC | #62
On 07/09/10 06:27, Mike Stump wrote:
> On Jul 5, 2010, at 1:59 AM, Nathan Sidwell wrote:
>> Do you have a reference to the C standard that clarifies the difference?
>
> Sure, it would be the part where the edits are when we ask them to regularize the rules with C++ after C++ fixes what they broke.  :-)  That is why I carefully chose the phrase `what we should do' as opposed to, what the standard says...
>
>> Wrt the std, all those expression-statements have values - the first is an lvalue, the other 2 are rvalues.
>
> Yeah, I know.
>
>> Another example I missed is:
>>   expr, vobj;
>>
>> I think that should read vobj.
>
> :-)  gcc fetches.  I'd not oppose that.
>
>> But AFAICT 'expr, vobj = data;' and 'expr, vobj;' are identically rvalued according to the C std.  Is there anything in the std that justifies them behaving differently?
>
> Nope, don't think so, at least in the C standard.

Good, so I think we're all agreed that what the C standard says is not the 
behaviour we want.

nathan
Nathan Sidwell July 16, 2010, 8:10 a.m. UTC | #63
This discussion appears to have gone quiet.  From the table in 
http://gcc.gnu.org/wiki/VolatileAccessComparison I think it clear that:

* GCC's current behaviour is inconsistent

* Historically GCC's behaviour has changed.  GCC 4.3's behaviour is particularly 
unfortunate WRT C and C++ consistency.

* The 2 other compilers tested (I'm lumping all the EDG-based ones together) 
implement different access patterns.  One of which appears to be the same as the 
current GCC development semantics.

* I missed another disambiguating testcase, namely '0, vobj = x;' in C the 
result of the comma operator is not an lvalue -- its result has the same 
properties as the result of the conditional operator.  That argues for them 
behaving the same -- which currently does not happen in GCC.

I think there is also consensus that what the standard literally says are not 
useful semantics.

Is there consensus on what the semantics should be?

Any suggestions as to how to move forward?

nathan
Mark Mitchell July 16, 2010, 3:20 p.m. UTC | #64
Nathan Sidwell wrote:

> Is there consensus on what the semantics should be?
> 
> Any suggestions as to how to move forward?

In the abstract, I think the EDG semantics make the most sense.   I
think the VC++ semantics are plausible, but extreme.  I think the
various in-between states that GCC has adopted over the years are just
woefully inconsistent.

So, my first preference would be to adopt the EDG semantics, and my
second preference would be to adopt the VC++/trunk semantics.  My
preference for the EDG semantics is somewhat offset to some extent by
the fact that our current semantics are "more like" GCC's past behavior,
i.e., they are more backward-compatible.

One of the reasons I do not like the VC++/trunk semantics are that using
assert-like macros will change behavior.  For example:

  #define check(X, Y) (X) ? (Y) : abort()
  check (condition, vobj = 3);

I think it's quite surprising that this generates a *read* from vobj,
even though:

  vobj = 3;

does not.

I would prefer not to insert reads except where absolutely required
because (a) they may change the state of hardware in surprising ways,
and (b) they cost cycles.  In short, by requiring rereads, we're setting
GCC up to underperform competing compilers on embedded systems.
Nathan Sidwell July 19, 2010, 8:41 a.m. UTC | #65
On 07/16/10 16:20, Mark Mitchell wrote:

> One of the reasons I do not like the VC++/trunk semantics are that using
> assert-like macros will change behavior.  For example:
>
>    #define check(X, Y) (X) ? (Y) : abort()
>    check (condition, vobj = 3);
>
> I think it's quite surprising that this generates a *read* from vobj,
> even though:

This is my composability argument.  With the reread semantics, we can't tell whether
    tmp = Frob (&volatile_thing, val);
and
    Frob (&volatile_thing, val);
have the same behaviour, unless we know that Frob is not a macro.

> I would prefer not to insert reads except where absolutely required
> because (a) they may change the state of hardware in surprising ways,
> and (b) they cost cycles.  In short, by requiring rereads, we're setting
> GCC up to underperform competing compilers on embedded systems.

I'd not thought of #b.

nathan
Nathan Sidwell Aug. 13, 2010, 9:41 a.m. UTC | #66
On 07/16/10 16:20, Mark Mitchell wrote:
> Nathan Sidwell wrote:
>
>> Is there consensus on what the semantics should be?
>>
>> Any suggestions as to how to move forward?
>
> In the abstract, I think the EDG semantics make the most sense.   I
> think the VC++ semantics are plausible, but extreme.  I think the
> various in-between states that GCC has adopted over the years are just
> woefully inconsistent.
>
> So, my first preference would be to adopt the EDG semantics, and my
> second preference would be to adopt the VC++/trunk semantics.  My
> preference for the EDG semantics is somewhat offset to some extent by
> the fact that our current semantics are "more like" GCC's past behavior,
> i.e., they are more backward-compatible.

There seems to have been no responses to your suggestion.  Can we move forward 
to reviewing the updated patch itself?

The patch is http://gcc.gnu.org/ml/gcc-patches/2010-06/msg02145.html
and Richard indicated he'd review it once semantics were agreed.

nathan
Mark Mitchell Aug. 18, 2010, 3:30 p.m. UTC | #67
Nathan Sidwell wrote:

>> So, my first preference would be to adopt the EDG semantics, and my
>> second preference would be to adopt the VC++/trunk semantics.  My
>> preference for the EDG semantics is somewhat offset to some extent by
>> the fact that our current semantics are "more like" GCC's past behavior,
>> i.e., they are more backward-compatible.
> 
> There seems to have been no responses to your suggestion.  Can we move
> forward to reviewing the updated patch itself?

Your patch implements *almost* the EDG semantics.  As I understand it,
you've implemented the EDG semantics, with one exception: that in C++,
vobj5 in this table:

http://gcc.gnu.org/wiki/VolatileAccessComparison

is reread.  In that way, you're consistent between C and C++, whereas
EDG, in C++, avoids the read.

I think it's better to be consistent between C and C++.  Real users
expect to be able to move code back and forth between the languages, and
this is a very subtle difference.

> The patch is http://gcc.gnu.org/ml/gcc-patches/2010-06/msg02145.html
> and Richard indicated he'd review it once semantics were agreed.

I think the patch is ready for review.  If Richard isn't able to review
it soon, I will do so.

I do think that we should have a section in our manual that documents
these semantics.  There are always corner cases, but I think that
something like the following is a reasonable start:

==
GCC never generates a read from a `volatile' object merely because that
object is being modified, unless the object is a bitfield.  (If the
object is a bitfield, the compiler may have to read the current value of
the bitfield, modify it, and store the new value.)

Concretely, none of the following statements result in a read of `vobj':

  int obj;
  volatile int vobj;
  vobj = 0;
  obj = vobj = 0;
  obj ? vobj = 0 : vobj = 1;
==

Would you please find a place to put that (or some modified form
thereof) in the manual?  I can review that in parallel with Richard
reviewing the core patch.

Thanks,
Richard Biener Aug. 18, 2010, 4 p.m. UTC | #68
On Wed, Aug 18, 2010 at 5:30 PM, Mark Mitchell <mark@codesourcery.com> wrote:
> Nathan Sidwell wrote:
>
>>> So, my first preference would be to adopt the EDG semantics, and my
>>> second preference would be to adopt the VC++/trunk semantics.  My
>>> preference for the EDG semantics is somewhat offset to some extent by
>>> the fact that our current semantics are "more like" GCC's past behavior,
>>> i.e., they are more backward-compatible.
>>
>> There seems to have been no responses to your suggestion.  Can we move
>> forward to reviewing the updated patch itself?
>
> Your patch implements *almost* the EDG semantics.  As I understand it,
> you've implemented the EDG semantics, with one exception: that in C++,
> vobj5 in this table:
>
> http://gcc.gnu.org/wiki/VolatileAccessComparison
>
> is reread.  In that way, you're consistent between C and C++, whereas
> EDG, in C++, avoids the read.
>
> I think it's better to be consistent between C and C++.  Real users
> expect to be able to move code back and forth between the languages, and
> this is a very subtle difference.
>
>> The patch is http://gcc.gnu.org/ml/gcc-patches/2010-06/msg02145.html
>> and Richard indicated he'd review it once semantics were agreed.
>
> I think the patch is ready for review.  If Richard isn't able to review
> it soon, I will do so.

The patch is ok, and we indeed should document these semantics.

Thanks,
Richard.


> I do think that we should have a section in our manual that documents
> these semantics.  There are always corner cases, but I think that
> something like the following is a reasonable start:
>
> ==
> GCC never generates a read from a `volatile' object merely because that
> object is being modified, unless the object is a bitfield.  (If the
> object is a bitfield, the compiler may have to read the current value of
> the bitfield, modify it, and store the new value.)
>
> Concretely, none of the following statements result in a read of `vobj':
>
>  int obj;
>  volatile int vobj;
>  vobj = 0;
>  obj = vobj = 0;
>  obj ? vobj = 0 : vobj = 1;
> ==
>
> Would you please find a place to put that (or some modified form
> thereof) in the manual?  I can review that in parallel with Richard
> reviewing the core patch.
>
> Thanks,
>
> --
> Mark Mitchell
> CodeSourcery
> mark@codesourcery.com
> (650) 331-3385 x713
>
Mike Stump Aug. 18, 2010, 5:26 p.m. UTC | #69
On Aug 18, 2010, at 9:00 AM, Richard Guenther wrote:
>> I think the patch is ready for review.  If Richard isn't able to review
>> it soon, I will do so.
> 
> The patch is ok, and we indeed should document these semantics.

Yes, please let's document the chosen semantics.  Would be nice to push the semantics into other compilers (clang, Visual C++) and the C and C++ standards groups as well.  Here's to hoping that everyone can agree...
H.J. Lu Aug. 20, 2010, 5:32 p.m. UTC | #70
On Fri, Aug 13, 2010 at 2:41 AM, Nathan Sidwell <nathan@codesourcery.com> wrote:
> On 07/16/10 16:20, Mark Mitchell wrote:
>>
>> Nathan Sidwell wrote:
>>
>>> Is there consensus on what the semantics should be?
>>>
>>> Any suggestions as to how to move forward?
>>
>> In the abstract, I think the EDG semantics make the most sense.   I
>> think the VC++ semantics are plausible, but extreme.  I think the
>> various in-between states that GCC has adopted over the years are just
>> woefully inconsistent.
>>
>> So, my first preference would be to adopt the EDG semantics, and my
>> second preference would be to adopt the VC++/trunk semantics.  My
>> preference for the EDG semantics is somewhat offset to some extent by
>> the fact that our current semantics are "more like" GCC's past behavior,
>> i.e., they are more backward-compatible.
>
> There seems to have been no responses to your suggestion.  Can we move
> forward to reviewing the updated patch itself?
>
> The patch is http://gcc.gnu.org/ml/gcc-patches/2010-06/msg02145.html
> and Richard indicated he'd review it once semantics were agreed.
>

Your patch caused:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45361
Nathan Sidwell Aug. 20, 2010, 6:10 p.m. UTC | #71
On 08/20/10 18:32, H.J. Lu wrote:

>
> Your patch caused:
>
> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45361

Is the behaviour of that test case changed by the gimple change?  what is the 
assembly output you are seeing -- is it merely regex tuning?

I.e. is this a regression, or is it lack of progression?

nathan
diff mbox

Patch

2010-06-21  Nathan Sidwell  <nathan@codesourcery.com>

	gcc/
	* gimplify.c (gimplify_modify_expr): When assigning to volatiles,
	copy the src value and return a copy.

	gcc/testsuite/
	* gcc.target/i386/volatile-2.c: New.

Index: gimplify.c
===================================================================
--- gimplify.c	(revision 160876)
+++ gimplify.c	(working copy)
@@ -4467,6 +4467,28 @@ 
   gcc_assert (TREE_CODE (*expr_p) == MODIFY_EXPR
 	      || TREE_CODE (*expr_p) == INIT_EXPR);
 
+  if (want_value && TREE_THIS_VOLATILE (TREE_TYPE (*to_p)))
+    {
+      /* copy the rhs to a temporary, store the temporary and then
+         return it.  This makes sure we consistently do not re-read a
+         volatile just after storing it.  */
+      tree lhs = *to_p;
+      tree rhs = *from_p;
+      tree nv_type = TYPE_MAIN_VARIANT (TREE_TYPE (lhs));
+      tree tmp = create_tmp_var (nv_type, "modtmp");
+      tree tmp_ass = build2 (MODIFY_EXPR, nv_type, tmp, rhs);
+      tree vol_ass = build2 (MODIFY_EXPR, nv_type, lhs, tmp);
+      
+      recalculate_side_effects (tmp_ass);
+      gimplify_and_add (tmp_ass, pre_p);
+
+      recalculate_side_effects (vol_ass);
+      gimplify_and_add (vol_ass, pre_p);
+
+      *expr_p = tmp;
+      return GS_OK;
+    }
+  
   /* Insert pointer conversions required by the middle-end that are not
      required by the frontend.  This fixes middle-end type checking for
      for example gcc.dg/redecl-6.c.  */
Index: testsuite/gcc.target/i386/volatile-2.c
===================================================================
--- testsuite/gcc.target/i386/volatile-2.c	(revision 0)
+++ testsuite/gcc.target/i386/volatile-2.c	(revision 0)
@@ -0,0 +1,92 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+/* Check volatiles are written, read or not re-read consistently */
+
+
+/* simple assignments */
+
+extern int volatile obj_0;
+void test_0 (int data)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_0" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_0," } } */
+  obj_0 = data;
+}
+
+extern int volatile obj_1;
+int test_1 (int data)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_1" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_1," } } */
+  return obj_1 = data;
+}
+
+extern int volatile obj_2;
+int test_2 (void)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_2" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_2," } } */
+  return obj_2 = 0;
+}
+
+
+/* Assignments in compound exprs */
+
+extern int volatile obj_3;
+int test_3 (int data)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_3" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_3," } } */
+  return (obj_3 = data, 0);
+}
+
+extern int volatile obj_4;
+int test_4 (void)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_4" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_4," } } */
+  return (obj_4 = 0, 0);
+}
+extern int volatile obj_5;
+int test_5 (void)
+{
+  /* should reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_5" } } */
+  /* { dg-final { scan-assembler "movl\[ \t\]obj_5," } } */
+  return (obj_5 = 0, obj_5);
+}
+
+/* Assignments in conditional exprs */
+
+extern int volatile obj_6;
+void test_6 (int data, int cond)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_6" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_6," } } */
+  cond ? obj_6 = data : 0;
+}
+
+extern int volatile obj_7;
+int test_7 (int data, int cond)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_7" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_7," } } */
+  return cond ? obj_7 = data : 0;
+}
+
+extern int volatile obj_8;
+int test_8 (int cond)
+{
+  /* should not reread obj */
+  /* { dg-final { scan-assembler "movl\[ \t\]\[^,\]+, obj_8" } } */
+  /* { dg-final { scan-assembler-not "movl\[ \t\]obj_8," } } */
+  return cond ? obj_8 = 0 : 0;
+}