Topic: throw/catch and sequence points


Author: Hyman Rosen <hyrosen@mail.com>
Date: Mon, 30 Jan 2006 00:57:02 CST
Raw View
Ben Hutchings wrote:
> Would you say that
>     int a = 0;
>     (0, a += 1, 0) + (0, a += 2, 0);
> has defined behaviour?  Do you think this is entirely clear from
> the current standard?

Yes, and yes. 5.18/1 states in the clearest of terms that given a
pair of expressions separated by the comma operator, the left one
is evaluated and its side effects are performed before the right
one is evaluated. That means that for the expression given, the
compiler may choose to evaluate the two operands of the '+' in
either order, but in evaluating each operand there is no choice.
The three subexpressions go in order, left-to-right. As 5/4 tells
you, order is unspecified except where otherwise noted, and 5.18/1
is one of those other notices.

> I, and I think Nick, think "sequence points" should be replaced by
> explicit definition of a partial ordering of operations.

Replacing sequence points by partial ordering is a fool's game.
It's going to cause a monumental waste of time as overlooked
corner cases come to light, just as you believe you are finding
problems with the sequence point notions. The right thing to do
is to specify the order completely. Computer programs exist in
order to direct the operations of a computer. There is zero
benefit in allowing those directions to be ambiguous. But as I've
said, I've been down this road before, and there is just no
convincing some people.

>> Throwing an exception within an expression causes it
>> to be complete, and therefore there is a sequence point after
>> such a throw.
>
> Please justify this statement with reference to the standard.

12.2/3

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: Mon, 30 Jan 2006 09:28:29 CST
Raw View
Hyman Rosen wrote:
> Ben Hutchings wrote:
> > Would you say that
> >     int a = 0;
> >     (0, a += 1, 0) + (0, a += 2, 0);

Let's label some of those sub-expressions as follows:

          (A , B, C) + (D, E, F);

For purposes of the discussion below, let t(A) represent the time at
which sub-expression A is completely evaluated. A "*" will indicate the
presence of a sequence point

> > has defined behaviour?  Do you think this is entirely clear from
> > the current standard?
>
> Yes, and yes. 5.18/1 states in the clearest of terms that given a
> pair of expressions separated by the comma operator, the left one
> is evaluated and its side effects are performed before the right
> one is evaluated.

That means that following relationships are guranteed:

t(A) < * < t(B) < * < t(C)

and

t(D) < * < t(E) < * < t(F)

And there's a sequence point corresponding to each of those "<".

> compiler may choose to evaluate the two operands of the '+' in
> either order, but in evaluating each operand there is no choice.

That gurantees only that all of the sub-expressions I've labelled must
have been evaluated before the + can be evaluated. It does not impose
any additional restrictions on the ordering of those sub-expressions
relatvie to each other. Therefore, one possible order is:

t(A) < t(D) < * < t(B) < t(E) < * <  t(C) < t(F)

Note that t(B) and t(E) both write a new value to a, without an
intervening sequence point. Therefore, for this ordering, the behaviour
is undefined. Therefore, as you indicated earlier, since there is one
permitted ordering with undefined behavior, according to 1.9p5, the
behavior is undefined.

This is one area where the C++ standard is much clearer than the C
standard. It's been argued that if there are any orderings permitted by
the C standard that have defined behavior, then an implementation is
obligated to implement behaviour equivalent to one of those orderings.
There's no textual support for this obligation, but the argument would
be more clearly incorrect if the C standard had something equivalent to
section 1.9p5  in the C++ standard.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Mon, 30 Jan 2006 12:48:13 CST
Raw View
Hyman Rosen wrote:

> 5.18/1 states in the clearest of terms that given a
> pair of expressions separated by the comma operator, the left one
> is evaluated and its side effects are performed before the right
> one is evaluated. That means that for the expression given, the
> compiler may choose to evaluate the two operands of the '+' in
> either order, but in evaluating each operand there is no choice.

You seem to be assuming that the compiler must finish evaluating one of
the operands of the '+' before it can begin to evaluate the other.  Is
this actually guaranteed by the standard?  I didn't think it was.

Given expressions A, B, C, and D, where A and C modify the same
variable:

 (A, B) + (C, D)

couldn't the compiler perform "A then C then  CDSeqPt then D then
ABSeqPt then B" (where CDSeqPt is the sequence point between C and D
and ABSeqPt is the seqence point between A and B)?

If it can, then there isn't an intervening sequence point between A and
C, so the behavior is undefined.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Mon, 30 Jan 2006 12:54:33 CST
Raw View
Nick Maclaren wrote:

> Sequence points are defined as locations in the parse tree, with
> semantic synchronisation effects in the abstract (execution) machine.

But C++ defines sequence points as points in the execution sequence,
not in the parse tree at all.  (1.9/7).

This means that the interpretation of "before" and "after" is trivial,
and doesn't require recourse to DAGs or any other cleverness, because
once the execution sequence is given (i.e. selected by the compiler),
you've got a nice linear ordering of all the execution steps.

Another way of putting it is that sequence points contstrain the
visibility of side effects, NOT the order of evaluation.  The compiler
selects an order of evaluation first (based on data dependencies and a
handful of specific language rules), then applies the required sequence
points.

The same source code (and parse tree) could give rise to several
different execution sequences, and if any sequence allowed by the
language rules is invalid, the program is said to exhibit undefined
behavior (5/4).

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Mon, 30 Jan 2006 12:55:09 CST
Raw View
Hyman Rosen wrote:
.
> Replacing sequence points by partial ordering is a fool's game.
> It's going to cause a monumental waste of time as overlooked
> corner cases come to light, just as you believe you are finding
> problems with the sequence point notions. The right thing to do
> is to specify the order completely. Computer programs exist in
> order to direct the operations of a computer. There is zero
> benefit in allowing those directions to be ambiguous. But as I've
> said, I've been down this road before, and there is just no
> convincing some people.

Consider this function:

int foo(int a, int b, int c, int d)
  {
    return(a + b + c + d);
  }

On a CPU with dual ALUs, it obviously makes sense for the
intermediate sums a + b and c + d to be calculated at the
same time.  Are you saying that this optimization should not
be allowed because it make the order of operations
ambiguous?  What is the great benefit that is received
in return for not using the hardware in the most optimal
way?

The C and C++ standards go even further, because
even if I write:

int bar(int a, int b, int c, int d)
  {
    a += b;
    a += c + d;
    return(a);
  }

a + b and c + d could still occur in parallel, because there
are no observable events in this code, so the order (or
simultinaity) of operations is completely arbitrary, so long
as the results are the same as the nominal order would
give.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Mon, 30 Jan 2006 19:42:35 GMT
Raw View
kuyper@wizard.net wrote:
> That means that following relationships are guranteed:
> t(A) < * < t(B) < * < t(C)
> and
> t(D) < * < t(E) < * < t(F)
> And there's a sequence point corresponding to each of those "<".
>
> Therefore, one possible order is:
> t(A) < t(D) < * < t(B) < t(E) < * <  t(C) < t(F)
>
> Note that t(B) and t(E) both write a new value to a, without an
> intervening sequence point. Therefore, for this ordering, the behaviour
> is undefined. Therefore, as you indicated earlier, since there is one
> permitted ordering with undefined behavior, according to 1.9p5, the
> behavior is undefined.

I don't agree. Your proof assumes that sequence points
exist independently of expressions. But 1.9/18 says that
     "In the evaluation of ... a , b ... there is a sequence
      point after the evaluation of the first expression."
I read that as meaning that the sequence point occurs once
the first expression has been evaluated. In your notation,
I believe we have
     t(A)* < t(B)* < t(C) and t(D)* < t(E)* < t(F)
and so we cannot have the t(B) < t(E) of your version.

Of course, plenty of people will not agree with this, and so
we have yet another reason why unspecified order of evaluation
is a horrible thing for a programming language to have. I
repeat that trying to preserve deliberate ambiguity while
trying to remove the perceived problems of sequence points is
doomed to failure; holes will be discovered in the new wording
which will be the equal of any now present.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Mon, 30 Jan 2006 19:47:48 GMT
Raw View
wkaras@yahoo.com wrote:
> int foo(int a, int b, int c, int d)
>   { return(a + b + c + d); }
>
> On a CPU with dual ALUs, it obviously makes sense for the
> intermediate sums a + b and c + d to be calculated at the
> same time.  Are you saying that this optimization should not
> be allowed because it make the order of operations
> ambiguous?

It should not be allowed, and in fact is not currently allowed,
if the result can differ from the canonical left-to-right order.
See 1.9/15.

> What is the great benefit that is received in return for not
 > using the hardware in the most optimal way?

Making a program mean only one thing. Since a program exists to
control the operation of a computer, allowing the control to be
ambiguous is dangerous.

> so long as the results are the same as the nominal order would
> give.

Well, yes, of course. The as-if rule isn't going to change.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Mon, 30 Jan 2006 14:51:25 CST
Raw View
wkaras@yahoo.com wrote:

> Consider this function:
>
> int foo(int a, int b, int c, int d)
>   {
>     return(a + b + c + d);
>   }
>
> On a CPU with dual ALUs, it obviously makes sense for the
> intermediate sums a + b and c + d to be calculated at the
> same time.

Obviously.  Except that the standard actually does forbid this.  5.7/1
specifies that additive operators group left-to-right, so the
expression

  a + b + c + d

must produce a result as-if from the evaluation of

 (((a + b) + c) + d)

which is NOT necessarily the same result as

 (a + b) + (c + d)

for signed integral types.  (The second version has undefined behavior
for some values of a, b, c and d, for which the first version is well
defined.)

The (non-normative) note at 1.9/15 makes the intent clear.


> The C and C++ standards go even further, because
> even if I write:
>
> int bar(int a, int b, int c, int d)
>   {
>     a += b;
>     a += c + d;
>     return(a);
>   }
>
> a + b and c + d could still occur in parallel, because there
> are no observable events in this code, so the order (or
> simultinaity) of operations is completely arbitrary, so long
> as the results are the same as the nominal order would
> give.


In discussions about strengthening the C++ order-of-evaluation rules,
it's important to keep in mind that NOBODY is talking about tampering
with the as-if rule.  If an optimization (such as re-ordering or
parallelizing a computation) is guaranteed to have no effect on the
observable behavior of the program, then all's well.

The real question is how much lattitude the compiler should have to
order computations in ways which may violate the as-if condition...i.e.
re-orderings which change the semantics of the program.  For example,
if f() and g() may have side-effects, and if we write:

  return f() + g();

do we really want the compiler to decide which function to call first?
Does it really hurt performance significantly to require the compiler
to call f() first?  Is the performance impact sufficiently significant
to make the increased risk of programmer error worthwhile?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Mon, 30 Jan 2006 21:35:06 CST
Raw View
johnchx2@yahoo.com wrote:
> wkaras@yahoo.com wrote:
>
> > Consider this function:
> >
> > int foo(int a, int b, int c, int d)
> >   {
> >     return(a + b + c + d);
> >   }
> >
> > On a CPU with dual ALUs, it obviously makes sense for the
> > intermediate sums a + b and c + d to be calculated at the
> > same time.
>
> Obviously.  Except that the standard actually does forbid this.  5.7/1
> specifies that additive operators group left-to-right, so the
> expression
>
>   a + b + c + d
>
> must produce a result as-if from the evaluation of
>
>  (((a + b) + c) + d)
>
> which is NOT necessarily the same result as
>
>  (a + b) + (c + d)
>
> for signed integral types.  (The second version has undefined behavior
> for some values of a, b, c and d, for which the first version is well
> defined.)
>
> The (non-normative) note at 1.9/15 makes the intent clear.

Sorry, I wasn't aware of this special case in the Standard for
CPUs that have exceptions for integer arithmetic overflow.  I've
never worked with a CPU which had that behavior.

>
> > The C and C++ standards go even further, because
> > even if I write:
> >
> > int bar(int a, int b, int c, int d)
> >   {
> >     a += b;
> >     a += c + d;
> >     return(a);
> >   }
> >
> > a + b and c + d could still occur in parallel, because there
> > are no observable events in this code, so the order (or
> > simultinaity) of operations is completely arbitrary, so long
> > as the results are the same as the nominal order would
> > give.
>
>
> In discussions about strengthening the C++ order-of-evaluation rules,
> it's important to keep in mind that NOBODY is talking about tampering
> with the as-if rule.  If an optimization (such as re-ordering or
> parallelizing a computation) is guaranteed to have no effect on the
> observable behavior of the program, then all's well.
>
> The real question is how much lattitude the compiler should have to
> order computations in ways which may violate the as-if condition...i.e.
> re-orderings which change the semantics of the program.  For example,
> if f() and g() may have side-effects, and if we write:
>
>   return f() + g();
>
> do we really want the compiler to decide which function to call first?
> Does it really hurt performance significantly to require the compiler
> to call f() first?  Is the performance impact sufficiently significant
> to make the increased risk of programmer error worthwhile?

Do we really want to stop a multi-core CPU from doing both
f() and g() at the same time because it's too complex for the
compiler to determine whether or not it would be the same
as excecuting f(), then g()?  It's very simple for the programmer
to tell the compiler that it can't reorder unless if's sure the
result would not change:

int i = f();
return i + g();

If stronger order-of-evaluation rules were accompanied by
convenent syntax for explicit multi-threading, that might be
OK.  Otherwise, C++ could be left very hamstrung
as parallel execution becomes more and more prevelant
in CPU architectures.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: Mon, 30 Jan 2006 21:55:25 CST
Raw View
Hyman Rosen wrote:
> kuyper@wizard.net wrote:
>

To clarify something that I didn't state as clearly as I should have:
for expression A, t(A) represents the time at which the evaluation of A
is complete, including side-effects.

>> That means that following relationships are guranteed:
>> t(A) < * < t(B) < * < t(C)
>> and
>> t(D) < * < t(E) < * < t(F)
>> And there's a sequence point corresponding to each of those "<".
>>
>> Therefore, one possible order is:
>> t(A) < t(D) < * < t(B) < t(E) < * <  t(C) < t(F)
>>
>> Note that t(B) and t(E) both write a new value to a, without an
>> intervening sequence point. Therefore, for this ordering, the behaviour
>> is undefined. Therefore, as you indicated earlier, since there is one
>> permitted ordering with undefined behavior, according to 1.9p5, the
>> behavior is undefined.
>
>
> I don't agree. Your proof assumes that sequence points
> exist independently of expressions.

That's either not a correct description of what I assumed, or it is a
correct description of sequence points, depending upon what you mean by
"independently". There's a close connection between expressions and
sequence points. For every sequence point, there's some expressions
that must come before it, and other expressions that must come after
it; it would not be accurate to say that a sequence point is
independent of those expressions. Even the expressions that are not
required to occur in a particular order relative to a given sequence
point are still constrained by it: they must occur either entirely
before or entirely after it.

However, while they aren't independent of expressions, sequence points
are seperate from the the expressions they sequence. Otherwise the
standard couldn't say that they occur after or before the expressions
they're connected with; it would have to always use a phrase like "at
the completion of", something it already currently does in only a few
places.

> But 1.9/18 says that
>     "In the evaluation of ... a , b ... there is a sequence
>      point after the evaluation of the first expression."
> I read that as meaning that the sequence point occurs once
> the first expression has been evaluated.

Yes, but the standard places no constraint on how long after the
evaluation of the first expression the sequence point occurs. In
particular, it says nothing to suggest that any significant events are
prohibited from happening between the evaluation of one expression and
the occurance of the associated sequence point.

> I believe we have
>     t(A)* < t(B)* < t(C) and t(D)* < t(E)* < t(F)
> and so we cannot have the t(B) < t(E) of your version.

Let s(A) be the time at which evaluation of expression A starts. Let
c(B) be the time at which the specific side-effect of updating the
value of 'a' during evaluation of B occurs.

Even if you were right, and the sequence point between B and C were
rigidly attached to the t(B), rather than being flexibly required to
occur at an unspecified time "after" t(B), that doesn't avoid the
problem. That's because s(B) <= c(B) <= t(B)

Because of sequence points, we have

t(A)* < s(B) <= c(B) <= t(B)* < s(C)

and also

t(D)* < s(E) <= c(E) <= t(E)* < s(F)

However,= there's nothing that prohibits

t(A)* < t(D)* < s(B) < s(E) < c(B) < c(E) < t(B)* < t(E)* < s(C) < s(F)

If that sequence were selected, there would be no sequence point
seperating the two side-effects. Since it's a legal sequence in which
to carry out those operations, the behaviour is undefined.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Tue, 31 Jan 2006 10:50:39 CST
Raw View
wkaras@yahoo.com wrote:

> Do we really want to stop a multi-core CPU from doing both
> f() and g() at the same time because it's too complex for the
> compiler to determine whether or not it would be the same
> as excecuting f(), then g()?

We already do: 1.9/8 prohibits parallelizing across function calls
(unless the compiler can prove that the as-if rule applies).

The business of letting the compiler transform a single threaded
program into a multi-threaded one is a kettle-of-fish all by itself,
and, yes, if the compiler wants to turn my single threaded program into
a multi-threaded one, then it should be required to prove that the
result is as safe and correct as the single-threaded version I thought
I wrote.

It's a pretty general rule (not just in C++, but in compiler design in
general) that an optimization isn't allowed to change the meaning of a
program.  And that's a good thing.

But the C++ standard sort of skirts this rule by saying that, if the
order of execution of f() and g() matters, then the expression f() +
g() has two possible meanings and either one will do just fine.  Which
is almost certainly not what the programmer thought it meant.


> It's very simple for the programmer
> to tell the compiler that it can't reorder unless if's sure the
> result would not change:
>
> int i = f();
> return i + g();
>

Yes, that's the current state of affairs in the standard, and it's a
pretty good compromise.  (And it's why this is a lingering controversy,
rather than an urgent DR fix...the status quo really is liveable.)

However, the status quo is known to lead to errors, and it's difficult
to teach effectively.  So it seems perfectly valid to ask whether the
real-world performance benefits actually outweigh the real-world costs,
even if the status quo is theoretically sound.

> If stronger order-of-evaluation rules were accompanied by
> convenent syntax for explicit multi-threading, that might be
> OK.  Otherwise, C++ could be left very hamstrung
> as parallel execution becomes more and more prevelant
> in CPU architectures.

I think that the real solution to this issue will be for compilers to
get "smarter".  We're already seeing more whole-program and link-time
optimization, for example.   The idea is to raise the bar for what is
"too complex" for the compiler to prove.

The standard may be able to help by standardizing function annotations
(like gcc's __pure attribute) that give the compiler more information
to use in the optimization process.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Tue, 31 Jan 2006 17:00:34 GMT
Raw View
wkaras@yahoo.com wrote:
> Do we really want to stop a multi-core CPU from doing both
> f() and g() at the same time because it's too complex for the
> compiler to determine whether or not it would be the same
> as excecuting f(), then g()?

Yes, of course. Otherwise, it becomes impossible to reason
about the behavior of a function and the correctness of a
program. And the C++ standard does forbid this in 1.9/8.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Tue, 31 Jan 2006 17:00:42 GMT
Raw View
kuyper@wizard.net wrote:
> However,= there's nothing that prohibits
> t(A)* < t(D)* < s(B) < s(E) < c(B) < c(E) < t(B)* < t(E)* < s(C) < s(F)

OK. Upon closer reading, I agree that the expression
has undefined behavior.

Can we get rid of sequence points now, and define
order of evaluation?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: John Nagle <nagle@animats.com>
Date: Tue, 31 Jan 2006 15:28:08 CST
Raw View
johnchx2@yahoo.com wrote:
> wkaras@yahoo.com wrote:
>
>
>>Do we really want to stop a multi-core CPU from doing both
>>f() and g() at the same time because it's too complex for the
>>compiler to determine whether or not it would be the same
>>as excecuting f(), then g()?
>
>
> We already do: 1.9/8 prohibits parallelizing across function calls
> (unless the compiler can prove that the as-if rule applies).
>
> The business of letting the compiler transform a single threaded
> program into a multi-threaded one is a kettle-of-fish all by itself,
> and, yes, if the compiler wants to turn my single threaded program into
> a multi-threaded one, then it should be required to prove that the
> result is as safe and correct as the single-threaded version I thought
> I wrote.

     One approach to the relationship between exceptions and parallelism
is to treat try-blocks as atomic operations.  If an exception is
raised in a try-block, then the state of the system should be returned
to the state before entry to the try-block.  If you take that
approach, a compiler can insert as much parallism as you want within
a try-block, as long as it can undo everything.

    If this is allowed to implementations, then parallelizing
compilers have another option, and one that allows more
parallelism than "as if sequential".  The intent here is that
compilers should be allowed to implement either "as-if" or
atomic try-blocks. (Should the user have control of this choice?)

    As a practical matter, this means a number-crunching
function that works on big matrices in parallel can be
compiled with massive paralllelism, and if somewhere an
exception is thrown, the output matrix (which is probably
junk anyway) just reverts to its original value, rather
than having the junk results up to the point of failure.

    CPU designers deal with such problems all the time.
The Pentium Pro/II/III actually implement "as if"
in hardware for hardware exceptions.  Floating point
overflow exceptions on IA-32 machines are precise, and
the state of the machine after the exception is
exactly as if the computation was sequential.  Even
though it very definitely is not.  There's a whole
"retirement unit" in the CPU devoted to handling
all the awful cases.  It's worth looking at how the
hardware designers deal with this.

    John Nagle
    Animats

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Tue, 31 Jan 2006 15:51:31 CST
Raw View
johnchx2@yahoo.com wrote:
> wkaras@yahoo.com wrote:
>
> > Do we really want to stop a multi-core CPU from doing both
> > f() and g() at the same time because it's too complex for the
> > compiler to determine whether or not it would be the same
> > as excecuting f(), then g()?
>
> We already do: 1.9/8 prohibits parallelizing across function calls
> (unless the compiler can prove that the as-if rule applies).

Your interpretation here seems reasonable.  I'm surprised by
your level of certainty, given how poorly written this clause is.
Is there some sort of "federalist papers" equivalent references
that can be looked at that help in the interpretation of some
of this really squirrelly stuff?

Seems like it'd be a very rare case where it's ok for either
f() to excute completely before g(), or g() completely before
f(), but not ok for f() and g() to overlap.  Why does the Standard
pointlessly prohibit what could be a great opportunity to
optimize on some architectures?

> The business of letting the compiler transform a single threaded
> program into a multi-threaded one is a kettle-of-fish all by itself,
> and, yes, if the compiler wants to turn my single threaded program into
> a multi-threaded one, then it should be required to prove that the
> result is as safe and correct as the single-threaded version I thought
> I wrote.
>
> It's a pretty general rule (not just in C++, but in compiler design in
> general) that an optimization isn't allowed to change the meaning of a
> program.  And that's a good thing.
>
> But the C++ standard sort of skirts this rule by saying that, if the
> order of execution of f() and g() matters, then the expression f() +
> g() has two possible meanings and either one will do just fine.  Which
> is almost certainly not what the programmer thought it meant.

I'd agree the ambiguity is useless if it doen't allow f() and g() 's
execution to be interleaved.

There seems to be some buzz that future multi-core CPUs
will favor functional rather than procedural coding.  Maybe
at some point, this bandwagon will get moving fast enough
so that C++ will want to jump on it by clearly allowing the
interleaving of functions in expressions like f() + g() .

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Wed, 1 Feb 2006 09:22:11 CST
Raw View
Hyman Rosen wrote:
> kuyper@wizard.net wrote:
> > However,= there's nothing that prohibits
> > t(A)* < t(D)* < s(B) < s(E) < c(B) < c(E) < t(B)* < t(E)* < s(C) < s(F)
>
> OK. Upon closer reading, I agree that the expression
> has undefined behavior.
>
> Can we get rid of sequence points now, and define
> order of evaluation?

Defining order of evaluation doesn't get rid of sequence points, it
just adds more of them.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Wed, 1 Feb 2006 10:42:08 CST
Raw View
wkaras@yahoo.com wrote:
> johnchx2@yahoo.com wrote:
> > wkaras@yahoo.com wrote:
> >
> > > Do we really want to stop a multi-core CPU from doing both
> > > f() and g() at the same time because it's too complex for the
> > > compiler to determine whether or not it would be the same
> > > as excecuting f(), then g()?
> >
> > We already do: 1.9/8 prohibits parallelizing across function calls
> > (unless the compiler can prove that the as-if rule applies).
>
> Your interpretation here seems reasonable.  I'm surprised by
> your level of certainty, given how poorly written this clause is.
> Is there some sort of "federalist papers" equivalent references
> that can be looked at that help in the interpretation of some
> of this really squirrelly stuff?

Well, in this case, the footnote to the paragraph ("In other words,
function executions do not interleave with each other") makes the
intent pretty clear, if it isn't apparent in the text.  FWIW, I don't
find 1.9/8 especially opaque, for standardese.

More generally, the closest equivalent to Madison's notes on the
Constitutional Convention would be the standards committees Issues
Lists:

http://www2.open-std.org/jtc1/sc22/wg21/docs/cwg_index.html
http://www2.open-std.org/jtc1/sc22/wg21/docs/lwg-index.html

(The links above are to the section indexes, which list defect reports
by the section of the standard to which they apply.)

The Issues Lists give all of the questions that have been officially
submitted to the commitee (including ambiguities and contradictions in
the standard), along with some of the resulting discussion and action
(if any).

The Federalist Papers of C++ would have to be Stroustrup's Design &
Evolution of C++.


> Seems like it'd be a very rare case where it's ok for either
> f() to excute completely before g(), or g() completely before
> f(), but not ok for f() and g() to overlap.

Not at all.  For example:

char a;
char b;

int f() { a = 1; return 0; }
int g() { b = 0; return 0; }

f() and g() don't even touch the same variables.  However, on some
architectures and for some compiler settings, it may be unsafe to
execute f() and g() in parallel.

Another, perhaps more familiar case, is the Meyers Singleton:

Foo& GetFoo() {
   static Foo* p = 0;
   if (!p) p = new Foo;
   return *p;
}

int f() {
  Foo& r = GetFoo();
  // do stuff
  return 0;
}
int g() {
  Foo& r = GetFoo();
  // do stuff
  return 0;
}

Once again, perfectly safe in sequence, total disaster in parallel.

There are many, many more examples.

> Why does the Standard
> pointlessly prohibit what could be a great opportunity to
> optimize on some architectures?

As you will probably guess, those who wrote it don't agree with your
assessment of its pointlessness.  You'll have to persuade them.

One key thing: nothing prevents any optimization that doesn't change
the meaning of the program.  But we do insist that the compiler not
turn a slow, correct program into a fast, wrong program.


> There seems to be some buzz that future multi-core CPUs
> will favor functional rather than procedural coding.  Maybe
> at some point, this bandwagon will get moving fast enough
> so that C++ will want to jump on it by clearly allowing the
> interleaving of functions in expressions like f() + g() .


Not if interleaving them leads to incorrect results, race conditions,
and the other horrors parallel execution is subject to.

More generally, requiring the compiler to preserve the illusion of
sequential execution is a feature of the language, not a bug.  It is a
valuable feature, because human beings reason better and more reliably
about sequences of actions.  It's how we think.  And bridging the gap
between how humans think and the way that machines work is one of the
things the compiler is supposed to accomplish.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Wed, 1 Feb 2006 18:46:30 GMT
Raw View
wkaras@yahoo.com wrote:
> Why does the Standard
> pointlessly prohibit what could be a great opportunity to
> optimize on some architectures?

Because it's impossible to reason about the correctness of a
function if other statements can be arbitrarily executed at the
same time.

> C++ will want to jump on it by clearly allowing the
> interleaving of functions in expressions like f() + g() .

No, never.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Wed, 1 Feb 2006 20:31:00 GMT
Raw View
Peter Dimov wrote:
> Defining order of evaluation doesn't get rid of sequence points, it
> just adds more of them.

Defining order of evaluation gets rid of sequence points
because side effects happen as part of the evaluation.
There is no longer any need for the concept; evaluating
i++ increments i, right then and there.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Wed, 1 Feb 2006 23:36:57 CST
Raw View
johnchx2@yahoo.com wrote:
.
> > Seems like it'd be a very rare case where it's ok for either
> > f() to excute completely before g(), or g() completely before
> > f(), but not ok for f() and g() to overlap.
>
> Not at all.  For example:
>
> char a;
> char b;
>
> int f() { a = 1; return 0; }
> int g() { b = 0; return 0; }
>
> f() and g() don't even touch the same variables.  However, on some
> architectures and for some compiler settings, it may be unsafe to
> execute f() and g() in parallel.
>
> Another, perhaps more familiar case, is the Meyers Singleton:
>
> Foo& GetFoo() {
>    static Foo* p = 0;
>    if (!p) p = new Foo;
>    return *p;
> }
>
> int f() {
>   Foo& r = GetFoo();
>   // do stuff
>   return 0;
> }
> int g() {
>   Foo& r = GetFoo();
>   // do stuff
>   return 0;
> }
>
> Once again, perfectly safe in sequence, total disaster in parallel.
>
> There are many, many more examples.

Your first example would be perfectly safe to parallelize on many
common multi-core architectures.  Your second example is
more pertinent, but I think you need to change the references
because, remember, it has to be right if you serialize in
either order.

> > Why does the Standard
> > pointlessly prohibit what could be a great opportunity to
> > optimize on some architectures?
>
> As you will probably guess, those who wrote it don't agree with your
> assessment of its pointlessness.  You'll have to persuade them.
>
> One key thing: nothing prevents any optimization that doesn't change
> the meaning of the program.  But we do insist that the compiler not
> turn a slow, correct program into a fast, wrong program.
>
>
> > There seems to be some buzz that future multi-core CPUs
> > will favor functional rather than procedural coding.  Maybe
> > at some point, this bandwagon will get moving fast enough
> > so that C++ will want to jump on it by clearly allowing the
> > interleaving of functions in expressions like f() + g() .
>
>
> Not if interleaving them leads to incorrect results, race conditions,
> and the other horrors parallel execution is subject to.
>
> More generally, requiring the compiler to preserve the illusion of
> sequential execution is a feature of the language, not a bug.  It is a
> valuable feature, because human beings reason better and more reliably
> about sequences of actions.  It's how we think.  And bridging the gap
> between how humans think and the way that machines work is one of the
> things the compiler is supposed to accomplish.

Let try to restate my position more clearly.  It would be a good thing
if Standard C++ allowed portable parallelism.  C++ generally
allows us to do useful things even if they may be dangerous, as
long as, by changing our code, we have some way of avoiding
the potential danger.  Right now it's probably too confusing for
funtional-style programming to implicitly allow parallelism.  But
if using functional programming to allow parallelism becomes
mainstream, then it won't be confusing or surprising to the
programmer.  Otherwise, lets find some other notation for
explicitly allowing parallelization with the programmer
being responsible to only use it when it isn't dangerous on
the architectures being targeted.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: Wed, 1 Feb 2006 23:38:11 CST
Raw View
Hyman Rosen wrote:
> Peter Dimov wrote:
> > Defining order of evaluation doesn't get rid of sequence points, it
> > just adds more of them.
>
> Defining order of evaluation gets rid of sequence points
> because side effects happen as part of the evaluation.

Within the context of the current standard, if you only changed it by
explicitly mandating the precise order of evaluation of every
expression, that would have essentially no effect on the problems
you're worrying about. The standard allows the side-effects of
evaluation of an expression to be out of sync with the evaluation
itself. Sequence points control exactly how far out of sync those
side-effects can be. Removing the possibility of side-effects being out
of sync with expression evaluation is exactly equivalent to inserting
sequence points seperating every operand of every operator. If sequence
points were in fact that ubiquitious, there would be simpler ways for
the standard to describe the requirements that wouldn't make use of the
term "sequence point". However, the apparant disappearance of sequence
points would merely be an illusion; the reality would be a dramatic
increase in the number of sequence points, combined with a change of
language so they're not referred to as such.

The thing about sequence points that makes them confusing isn't their
presence, it's their absence.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Sean Kelly" <sean@f4.ca>
Date: Wed, 1 Feb 2006 23:36:15 CST
Raw View
John Nagle wrote:
>
>      One approach to the relationship between exceptions and parallelism
> is to treat try-blocks as atomic operations.  If an exception is
> raised in a try-block, then the state of the system should be returned
> to the state before entry to the try-block.  If you take that
> approach, a compiler can insert as much parallism as you want within
> a try-block, as long as it can undo everything.

This would basically be implementing a form of software transactional
memory in C++, with try block completion signalling transaction
completion.  But while I find transactions appealing in the general
sense, I'm not certain they would be particularly useful in this
specific case.  How should nested try blocks be handled?  And with
dynamic memory use being quite common, how often will the compiler
actually be able to determine whether it will be able to undo some
sequence of operations?  Concurrent code is another
concern--particularly code where the implementor relied on hardware
access ordering rules to implement the algorithm rather than by using
explicit memory barriers.

>     If this is allowed to implementations, then parallelizing
> compilers have another option, and one that allows more
> parallelism than "as if sequential".  The intent here is that
> compilers should be allowed to implement either "as-if" or
> atomic try-blocks. (Should the user have control of this choice?)

A lack of consistency here could be an issue, as this affects
observable behavior.  And making it a compiler switch just doesn't seem
like a good idea all around.

>     As a practical matter, this means a number-crunching
> function that works on big matrices in parallel can be
> compiled with massive paralllelism, and if somewhere an
> exception is thrown, the output matrix (which is probably
> junk anyway) just reverts to its original value, rather
> than having the junk results up to the point of failure.
>
>     CPU designers deal with such problems all the time.
> The Pentium Pro/II/III actually implement "as if"
> in hardware for hardware exceptions.  Floating point
> overflow exceptions on IA-32 machines are precise, and
> the state of the machine after the exception is
> exactly as if the computation was sequential.  Even
> though it very definitely is not.  There's a whole
> "retirement unit" in the CPU devoted to handling
> all the awful cases.  It's worth looking at how the
> hardware designers deal with this.

I'll admit that this is a very appealing idea, but I wonder how this
could be accomplished without significant memory overhead and in a
manner that does not cause surprising behavior for the uninitiated.  A
true implementation of software transactions may be more appropriate,
but I'm not sure that research has progressed to the point where a
convincing proposal could be made.  Perhaps someone with more
experience in the area can comment?


Sean

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: Thu, 2 Feb 2006 09:38:26 CST
Raw View
In article <1138816341.393711.204480@o13g2000cwo.googlegroups.com>, Sean
Kelly <sean@f4.ca> writes
>I'll admit that this is a very appealing idea, but I wonder how this
>could be accomplished without significant memory overhead and in a
>manner that does not cause surprising behavior for the uninitiated.  A
>true implementation of software transactions may be more appropriate,
>but I'm not sure that research has progressed to the point where a
>convincing proposal could be made.  Perhaps someone with more
>experience in the area can comment?

And I think this is an important element of this discussion. The
overwhelming majority of those involved in designing languages such as
C++ have very little, if any, experience of using  true parallelism. It
did not matter during the 70s, 80s and 90s because there were so many
other special needs for the big multi-processor number crunchers that
they needed special languages such as High Performance Fortran if they
were to be used safely and efficiently.

The multi-threading which Posix supported was much more an
organisational convenience for programmers working on hardware with pure
sequential processing. It allowed the programmer to conceptually do
several things even though there was a single processor doing the  work.
However when we move to true parallelism in processing we really do need
to think again and seek advice and help from those who have a
substantial understanding and experience of parallel processing.

Please note that parallel processing isn't just executing separate
threads on different processors. For example a mutex in a single
processor environment is not usually expensive, we do have to understand
and avoid deadlock, but usually the processor utilisation will remain
high (or even be enhanced). In a multi-processor environment, requiring
that a piece of code be executed in isolation with all other processes
locked out is fairly bad news on a dual processor/core machine. It is
tantamount to a disaster on a 10 or 100 processor/core machine, reducing
efficiency by between 90% and 99%. Doing that once in a while might be
acceptable but only for special circumstances.

In addition, it might well be worth the overhead of copying the data,
processing the copy and copying the result back (or taking a security
copy for a roll back if one is needed while processing the original --
saves a copy process when nothing goes wrong) in order to ensure that
the data integrity is preserved if any sub-process throws and exception.
Do not think of just two parallel threads of execution, but rather of 10
or more. In fact in this circumstance we can actually increase our data
integrity (AKA exception safety) because an exception raised anywhere
will result in roll back to the original.


--
Francis Glassborow      ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Stephen Howe" <sjhoweATdialDOTpipexDOTcom@eu.uu.net>
Date: Thu, 2 Feb 2006 12:28:40 CST
Raw View
> Let try to restate my position more clearly.  It would be a good thing
> if Standard C++ allowed portable parallelism.  C++ generally
> allows us to do useful things even if they may be dangerous, as
> long as, by changing our code, we have some way of avoiding
> the potential danger.  Right now it's probably too confusing for
> funtional-style programming to implicitly allow parallelism.  But
> if using functional programming to allow parallelism becomes
> mainstream, then it won't be confusing or surprising to the
> programmer.  Otherwise, lets find some other notation for
> explicitly allowing parallelization with the programmer
> being responsible to only use it when it isn't dangerous on
> the architectures being targeted.

I think what you are saying is extremely important.
We have over the past 60 years come as about as far as we can go with the
Von Neumann single execution unit as we can go.
We face a world where to get more performance improvements, execution must
be done in parallel.
Herb Sutter and others have been saying this for the past few years.

But it is not entirely clear what needs changing in the C++ standard to meet
these needs.
Whatever it is, it has to be such that ordinary programmers can write C++
programs that run correctly on such parallel architectures without having to
spend days looking at the program and eventually saying, "yes it is correct
from a parallel point of view".

Stephen Howe


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: francis@robinton.demon.co.uk (Francis Glassborow)
Date: Thu, 2 Feb 2006 18:41:29 GMT
Raw View
In article <1138824310.911250.95370@g47g2000cwa.googlegroups.com>,
wkaras@yahoo.com writes
>Let try to restate my position more clearly.  It would be a good thing
>if Standard C++ allowed portable parallelism.  C++ generally
>allows us to do useful things even if they may be dangerous, as
>long as, by changing our code, we have some way of avoiding
>the potential danger.  Right now it's probably too confusing for
>funtional-style programming to implicitly allow parallelism.  But
>if using functional programming to allow parallelism becomes
>mainstream, then it won't be confusing or surprising to the
>programmer.  Otherwise, lets find some other notation for
>explicitly allowing parallelization with the programmer
>being responsible to only use it when it isn't dangerous on
>the architectures being targeted.

IOWs, let us consider the experience with languages such as OCCAM and
consider how we can introduce syntax so that the programmer can
explicitly allow parallelizing sections of code. Or alternatively,
introduce syntax to allow the programmer to forbid it.

I am far from an expert on the subject but perhaps a single extra
keyword, parallel, would suffice if we then allowed the syntactic forms:

parallel for(obj in container ){
   // code processing an obj instance
}
OK to dispatch different instances to distinct cores/cpus

parallel {
    list of statements
}
OK to evaluate the statements in parallel

obj = parallel ( f() + g() );

OK to evaluate the top level sub-expressions in parallel




--
Francis Glassborow      ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Thu, 2 Feb 2006 18:51:12 GMT
Raw View
kuyper@wizard.net wrote:
> However, the apparant disappearance of sequence
> points would merely be an illusion; the reality would be a dramatic
> increase in the number of sequence points, combined with a change of
> language so they're not referred to as such.

OK. So what?

> The thing about sequence points that makes them confusing isn't their
> presence, it's their absence.

Again, so what? This discussion arose in the context of some
people (including me!) being confused by whether some expressions
have undefined behavior. Defining order of execution and making
side effects happen when the subexpression causing them is
evaluated eliminates the confusion, and is extremely easy to
explain and implement. By doing this, sequence points will no
longer be absent or mentioned, and then as you say, there will
not be any confusion.

Please read <http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.7>
for the prime example of how easy and clear it is to specify
what an expression means when you don't have C++'s ambiguity mess.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: Thu, 2 Feb 2006 14:49:48 CST
Raw View
Hyman Rosen wrote:
> kuyper@wizard.net wrote:
> > However, the apparant disappearance of sequence
> > points would merely be an illusion; the reality would be a dramatic
> > increase in the number of sequence points, combined with a change of
> > language so they're not referred to as such.
>
> OK. So what?

I was responding your rejection of Peter Dimov's correct assertion that
your suggested change corresponds to increasing the number of sequence
points. Your rejection of that assertion was inappropriate, and I was
merely explaining why.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Thu, 2 Feb 2006 21:19:12 GMT
Raw View
On 2006-02-02 18:41, Francis Glassborow wrote:
:
> I am far from an expert on the subject but perhaps a single extra
> keyword, parallel, would suffice if we then allowed the syntactic forms:
>
> parallel for(obj in container ){
>    // code processing an obj instance
> }
> OK to dispatch different instances to distinct cores/cpus
>
> parallel {
>     list of statements
> }
> OK to evaluate the statements in parallel

The former can be implemented using the latter.
Pseudo-code:

   parallel_for_each(begin, end, f)
   {
      if (begin != end)
      {
         next = begin;
         parallel
         {
            f(*begin);
            parallel_for_each(++next, end, f);
         }
      }
   }

Any optimizer that knows about tail recursion should be able to
recognize this for what it is.

> obj = parallel ( f() + g() );
>
> OK to evaluate the top level sub-expressions in parallel

That would probably be too restrictive and confusing, since

   obj = parallel ( f() + g() + h() );

would not meet the likely expectation that all three calls are
parallelized.

-- Niklas Matthies

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: John Nagle <nagle@animats.com>
Date: Thu, 2 Feb 2006 16:29:26 CST
Raw View
John Nagle wrote:
 >   We're seeing major changes in the compute engine world.
 > Multiprocessors are finally going mainstream.  Non shared
 > memory machines, like cell processors, are about to ship.
 > Game machines have vector processors.  Graphics boards
 > are now programmable, massively parallel, floating point engines.
 > "Physics chips" are coming.  The "normal case" of
 > one sequential CPU really isn't the normal case any more.
 >
 >   C++ provides no help in dealing with these new architectures.
 > Zero.  This is a problem.

   On that subject, there's a talk at the March 2006 Game
Developer's Conference on "GDC 2006: C++ on Next-Gen Consoles: Effective
Code for New Architectures".  That's exactly what I'm
talking about.

   John Nagle
   Animats

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nagle@animats.com (John Nagle)
Date: Sat, 4 Feb 2006 03:50:55 GMT
Raw View
Sean Kelly wrote:
> John Nagle wrote:
>
>>     One approach to the relationship between exceptions and parallelism
>>is to treat try-blocks as atomic operations. ...

>
> I'll admit that this is a very appealing idea, but I wonder how this
> could be accomplished without significant memory overhead and in a
> manner that does not cause surprising behavior for the uninitiated.  A
> true implementation of software transactions may be more appropriate,
> but I'm not sure that research has progressed to the point where a
> convincing proposal could be made.  Perhaps someone with more
> experience in the area can comment?

    After some thought, I agree that this idea, while elegant,
is way ahead of the state of the art.  People need to be
thinking about things like this as the number of CPUs on
mass-market machines goes up, but it probably needs to
be part of a major rethink of parallel programming.

    I've done considerable work on "physics engines" for
games and animation, where people do face the problems of
doing serious parallel number-crunching in mass market
products.  It would be very helpful if more language
support was available in this area.

    We're seeing major changes in the compute engine world.
Multiprocessors are finally going mainstream.  Non shared
memory machines, like cell processors, are about to ship.
Game machines have vector processors.  Graphics boards
are now programmable, massively parallel, floating point engines.
"Physics chips" are coming.  The "normal case" of
one sequential CPU really isn't the normal case any more.

    C++ provides no help in dealing with these new architectures.
Zero.  This is a problem.

   John Nagle
   Animats

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Fri, 3 Feb 2006 22:23:52 CST
Raw View
Francis Glassborow wrote:
.
> In addition, it might well be worth the overhead of copying the data,
> processing the copy and copying the result back (or taking a security
> copy for a roll back if one is needed while processing the original --
> saves a copy process when nothing goes wrong) in order to ensure that
> the data integrity is preserved if any sub-process throws and exception.
> Do not think of just two parallel threads of execution, but rather of 10
> or more. In fact in this circumstance we can actually increase our data
> integrity (AKA exception safety) because an exception raised anywhere
> will result in roll back to the original.
.

I would think that, if a thread throws, and the stack unwind reaches
the "fork" point without being caught, this would trigger a thow in
all other "tine" threads in the fork.  Or are you seeing a way to avoid
this?

This of course means biting the dreaded "asynchronous" thow
bullet.  So for the zero-time-overhead-without-throws expection
implementation, the associative lookup of address to
object cleanup code would have to handle any code address,
rather than just throw and return point.  While not trivial, this
seems doable.  And once the async throws are there, it's easier
to bring signals and hardware exceptions into the standardized
exception picture.

At the same time, it might be good if parallelism could be
supported without explicitly introducing the concept of
a thread into the Standard.  Maybe by weakening (rather
that strengthening) the required parital ordering of
sequence points (either implicitly or with new syntax).
Under the current concepts of sequence points and
observable behavior, it seems like it would be
theoretically Standard-compliant for a compiler to produce
an FPGA or CPLD load that could generate
all observable program behavior simultaneously.
C++ may be just a memory by the time such a thing
is remotely possible, but you never know.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Sun, 5 Feb 2006 19:29:56 GMT
Raw View
wkaras@yahoo.com wrote:
> I would think that, if a thread throws, and the stack unwind reaches
> the "fork" point without being caught, this would trigger a thow in
> all other "tine" threads in the fork.  Or are you seeing a way to avoid
> this?

I wouldn't. I would have an unhandled exception in a thread simply
terminate that thread. Then the rest of the program could find out
about the termination synchronously.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: bart@ingen.ddns.info (Bart van Ingen Schenau)
Date: Mon, 6 Feb 2006 20:54:02 GMT
Raw View
Hyman Rosen wrote:

> wkaras@yahoo.com wrote:
>> I would think that, if a thread throws, and the stack unwind reaches
>> the "fork" point without being caught, this would trigger a thow in
>> all other "tine" threads in the fork.  Or are you seeing a way to
>> avoid this?
>
> I wouldn't. I would have an unhandled exception in a thread simply
> terminate that thread. Then the rest of the program could find out
> about the termination synchronously.

I would let that depend on why those threads were created.
If the programmer created a particular thread, then an unhandled
exception in that thread should only terminate the one thread.
If the compiler introduced some threads, to make better use of the
parallel processing capabilities of the platform, then an unhandled
exception in one of those threads should also kill the other, related,
threads. Otherwise, it becomes impossible to discuss the behaviour of a
program after an exception has been thrown.

Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://www.eskimo.com/~scs/C-faq/top.html
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Mon, 6 Feb 2006 22:10:33 GMT
Raw View
Bart van Ingen Schenau wrote:
> If the compiler introduced some threads, to make better use of the
> parallel processing capabilities of the platform, then an unhandled
> exception in one of those threads should also kill the other, related,
> threads.

That's not going to happen. Throwing an exception is a macroscopic
and complex event. Compilers aren't going to be parallelizing over
code that throws by themselves - I'm sure that any such attempt will
lead to instances of the halting problem.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Wed, 25 Jan 2006 20:02:46 GMT
Raw View
Well, I sent this to a couple of relevent reflectors, and got a
stunned silence - not a new experience for me - so let's see if
this newsgroup does better :-)

Please do NOT answer it unless you have a more than casual knowledge
of the sequence point morass, as I can assure you that it is an evil
question.  The same applies to the implementation aspects.  But note
that I am not a C++ programmer!




Well, either I have seriously omitted to find the reference, or there
is a serious omission in the C++ standard :-)

1.7 paragraph 17 says that there is a sequence point after the copying
of a return value and before the execution of any expressions outside
the function, and the attached footnote makes it clear that this was
intended to apply to throw and catch.

Er, but isn't the point about throwing an exception within a function
and catching it by a handler outside that function (perhaps in a quite
different file) that the return value is NOT copied?

Now, for reasons that I can attempt to explain in ghastly detail (but
will almost certainly not make clear), this is a real problem and is
much more serious for threaded codes than for serial ones.  I have
been caught by it, badly, on some systems.

The simplest example is a function that evaluates an expression that
updates an object A while (not sequence-point separated) throwing an
exception B.  One interpretation of sequence points is that the handler
is then called in a nonce sub-tree rooted at the point the exception
is raised.  That is, actually, how a large proportion of hardware
works.

This means that there is no sequence-point ordering between the
update of A and the execution of the handler, and the programmer has
no power to introduce any.  So what happens when the handler completes?
Well, in practice, nothing special.  This is BAD news.  As far as the
standard goes, that is pretty hard to justify, but I can assure you
that it is what implementations will do.

What almost all systems do, after the problem has bit badly enough
that they realise it must be fixed, is to put a barrier somewhere
around the throw/catch (it doesn't matter where).  Unless I have
missed something critical, C++ needs to do the same.

Note that this ISN'T just relevant to hardware-raised exceptions,
but applies even to the most explicit throw/catch clauses on systems
with sufficiently loose memory ordering.

So I am right or confused?


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Wed, 25 Jan 2006 22:52:13 GMT
Raw View
Nick Maclaren wrote:
> 1.7 paragraph 17 says that there is a sequence point after the copying
> of a return value and before the execution of any expressions outside
> the function

That's 1.9/17, not 1.7/17.

> Er, but isn't the point about throwing an exception within a function
> and catching it by a handler outside that function (perhaps in a quite
> different file) that the return value is NOT copied?

So? 1.9/17 tells you that there are two sequence points.
There's one after copying the returned value, and there's
one before execution of any expressions outside the
function. Since the exception handler is most assuredly
outside the function, a sequence point has been encountered
before the handler receives control.

> much more serious for threaded codes than for serial ones

The C++ standard does not address multi-threaded programming,
so you need to check with your implementation if you need some
special effect.

> The simplest example is a function that evaluates an expression that
> updates an object A while (not sequence-point separated) throwing an
> exception B.  One interpretation of sequence points is that the handler
> is then called in a nonce sub-tree rooted at the point the exception
> is raised.  That is, actually, how a large proportion of hardware
> works.

What has hardware to do with C++? At the very least, stack
unwinding needs to be done up to the exception handler. The
only "interpretation" of sequence point is 1.9/7, which says
that at sequence points, previous side effects are complete
and future side effects haven't happened yet. They are not
related to exceptions, or to concurrent programming. (And
they should be done away with completely, but that's another
thread.)

> So I am right or confused?

Confused.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: invalid@bigfoot.com (Bob Hairgrove)
Date: Wed, 25 Jan 2006 22:52:45 GMT
Raw View
On Wed, 25 Jan 2006 20:02:46 GMT, nmm1@cus.cam.ac.uk (Nick Maclaren)
wrote:

[snip]
>note that I am not a C++ programmer!

OK...

>Well, either I have seriously omitted to find the reference, or there
>is a serious omission in the C++ standard :-)
>
>1.7 paragraph 17 says that there is a sequence point after the copying
>of a return value and before the execution of any expressions outside
>the function, and the attached footnote makes it clear that this was
>intended to apply to throw and catch.
>
>Er, but isn't the point about throwing an exception within a function
>and catching it by a handler outside that function (perhaps in a quite
>different file) that the return value is NOT copied?

As you say, there is a sequence point after the copying of a return
value. However, that doesn't mean that there are no sequence points
before that. 1.7 paragraph 11 tells you what a sequence point is ...
is that what you think it is?

>Now, for reasons that I can attempt to explain in ghastly detail (but
>will almost certainly not make clear), this is a real problem and is
>much more serious for threaded codes than for serial ones.  I have
>been caught by it, badly, on some systems.

If you are not a C++ programmer, please explain how have you been
"caught by it" and how this relates to the C++ language?

>The simplest example is a function that evaluates an expression that
>updates an object A while (not sequence-point separated) throwing an
>exception B.  One interpretation of sequence points is that the handler
>is then called in a nonce sub-tree rooted at the point the exception
>is raised.  That is, actually, how a large proportion of hardware
>works.

What is a "nonce sub-tree"?

>This means that there is no sequence-point ordering between the
>update of A and the execution of the handler, and the programmer has
>no power to introduce any.  So what happens when the handler completes?
>Well, in practice, nothing special.  This is BAD news.  As far as the
>standard goes, that is pretty hard to justify, but I can assure you
>that it is what implementations will do.

Whether or not an "update of A" is successful in the event of an
exception depends on the cv-qualification of A in the case of a
built-in primitive type (please refer to the keyword "volatile" in
this context), or if A is of non-POD type, whether it has been made
exception-safe, including whether or not proper synchronization has
occurred in a multi-threaded environment. But C++ has no built-in
language features concerning threading. Synchronization always depends
to a large extent on platform-specific mechanisms.

>What almost all systems do, after the problem has bit badly enough
>that they realise it must be fixed, is to put a barrier somewhere
>around the throw/catch (it doesn't matter where).  Unless I have
>missed something critical, C++ needs to do the same.

Can you please give us a concrete example? What kind of barrier are
you referring to?

>Note that this ISN'T just relevant to hardware-raised exceptions,
>but applies even to the most explicit throw/catch clauses on systems
>with sufficiently loose memory ordering.

Since you are not a C++ programmer, you probably aren't aware that C++
exceptions and hardware exceptions are not the same thing. There are
C++ implementations (compilers) out there which will somehow map
hardware exceptions to C++ exceptions using OS-specific mechanisms.
For example, some C++ compilers will use SEH on Windows to trap
hardware exceptions and throw a C++ exception which can then be caught
by C++ code. Otherwise, there is no C++ built-in mechanism to catch
hardware exceptions. (How could there be if C++ is a true
cross-platform language?) Another thing is threads: C++ has no
language features (yet) which deal with multi-threading issues in a
cross-platform manner.

>So I am right or confused?

You are probably confused as to what a sequence point is ... lots of
C++ programmers are, and since you aren't a C++ programmer, it
wouldn't be surprising.

--
Bob Hairgrove
NoSpamPlease@Home.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: francis@robinton.demon.co.uk (Francis Glassborow)
Date: Thu, 26 Jan 2006 01:54:25 GMT
Raw View
In article <a7nft115m54bime7trdj1u6v3u7abb615m@4ax.com>, Bob Hairgrove
<invalid@bigfoot.com> writes
>>So I am right or confused?
>
>You are probably confused as to what a sequence point is ... lots of
>C++ programmers are, and since you aren't a C++ programmer, it
>wouldn't be surprising.


I very much doubt that Nick is in any way confused as to what a sequence
point is. He is an outstanding C programmer on heavy duty hardware. I
suspect his main concern is what is about to happen as multi-core CPUs
become the norm on the desk-top. Are we going to have to learn the hard
lessons (learnt on main-frames) all over again.

Please try to take his concerns seriously even if understanding them
proves difficult (but the area he is considering is fraught with
surprises and unexpected difficulties that many programmers are unable
to grasp)

--
Francis Glassborow      ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Alberto Ganesh Barbati <AlbertoBarbati@libero.it>
Date: Wed, 25 Jan 2006 23:36:10 CST
Raw View
Nick Maclaren wrote:
> 1.7 paragraph 17 says that there is a sequence point after the copying
> of a return value and before the execution of any expressions outside
> the function, and the attached footnote makes it clear that this was
> intended to apply to throw and catch.

It's 1.9/17. Footnote 11 is not intended to "apply to throw and catch",
but the committee thinks it's a necessary clarification because there
are "more ways in which a called function can terminate its execution,
such as the throw of an exception." There's a big difference.

> Er, but isn't the point about throwing an exception within a function
> and catching it by a handler outside that function (perhaps in a quite
> different file) that the return value is NOT copied?

An exception may be thrown by the destructor of a local object after the
return value has been copied. In C++, as opposed to C, copying the
return value is not the last thing performed before leaving the function
(thus, probably, the need for footnote 11).

> The simplest example is a function that evaluates an expression that
> updates an object A while (not sequence-point separated) throwing an
> exception B.  One interpretation of sequence points is that the handler
> is then called in a nonce sub-tree rooted at the point the exception
> is raised.  That is, actually, how a large proportion of hardware
> works.

Your simplest example could be made even more simple if you could
provide us with a piece of code. Without code it's very difficult (for
me, at least) to visualize the problem and follow you. In particular, I
cannot figure out how an expression could update an object and throw
without a separating sequence-point. I'm sure you have an example ready,
could you post it, please?

> So I am right or confused?

I can't tell about you, but I *am* confused.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: John Nagle <nagle@animats.com>
Date: Thu, 26 Jan 2006 09:55:48 CST
Raw View
Francis Glassborow wrote:
> In article <a7nft115m54bime7trdj1u6v3u7abb615m@4ax.com>, Bob Hairgrove
> <invalid@bigfoot.com> writes
>
>>> So I am right or confused?

> Please try to take his concerns seriously even if understanding them
> proves difficult (but the area he is considering is fraught with
> surprises and unexpected difficulties that many programmers are unable
> to grasp)

    There are two basic problems worth thinking about here,
and it's not clear which one the original poster is trying
to discuss.

    First, there's the problem of converting a machine exception
into a C++ exception.  C++, unlike Ada, does not generally
support that at all, although some implementations do try to
make it work.  But that's a language extension.

    Second, there's the issue of what happens when the compiler
has introduced paralleism into a sequential program as an
optimization, and one of the threads throws an exception.
The original poster seems to be trying to figure out just
how conservative the C++ standard requires the compiler to be
in such situations.  SGI has (had?) compilers which did such
optimizations, and so this problem is more than theoretical.

    Consider, for example:

 inline float f(float x)
 { ... } // expensive function with no side effects
   // but which could raise an exception

 float tab1[100000];
 float tab2[100000];
 ...
 for (int i=0; i<100000; i++)
 { tab2[i] = f(tab1[i]); }

A multithreading compiler might try to optimize this by putting
a few threads to work on the problem.  That works out fine,
unless some call to f raises an exception.  Should that occur,
"tab1" would be partially updated, but the updated elements
might not necessarily be sequential.

    John Nagle
    Animats

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Thu, 26 Jan 2006 17:12:31 GMT
Raw View
In article <1BRBf.62798$eD5.1046673@twister2.libero.it>,
Alberto Ganesh Barbati <AlbertoBarbati@libero.it> writes:
|> Nick Maclaren wrote:
|> > 1.7 paragraph 17 says that there is a sequence point after the copying
|> > of a return value and before the execution of any expressions outside
|> > the function, and the attached footnote makes it clear that this was
|> > intended to apply to throw and catch.
|>
|> It's 1.9/17. Footnote 11 is not intended to "apply to throw and catch",
|> but the committee thinks it's a necessary clarification because there
|> are "more ways in which a called function can terminate its execution,
|> such as the throw of an exception." There's a big difference.

Oops.  Yes.  Thanks for the correction and explanation - that is
very useful.

|> An exception may be thrown by the destructor of a local object after the
|> return value has been copied. In C++, as opposed to C, copying the
|> return value is not the last thing performed before leaving the function
|> (thus, probably, the need for footnote 11).

Ugh.  That is even more important to me!

|> Your simplest example could be made even more simple if you could
|> provide us with a piece of code. Without code it's very difficult (for
|> me, at least) to visualize the problem and follow you. In particular, I
|> cannot figure out how an expression could update an object and throw
|> without a separating sequence-point. I'm sure you have an example ready,
|> could you post it, please?

    (a = 0) + (b + c);

Where b+c overflows.  There are more complex examples that can occur
even in the cleanest of code.


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: invalid@bigfoot.com (Bob Hairgrove)
Date: Thu, 26 Jan 2006 17:13:47 GMT
Raw View
On Thu, 26 Jan 2006 01:54:25 GMT, francis@robinton.demon.co.uk
(Francis Glassborow) wrote:

>I very much doubt that Nick is in any way confused as to what a sequence
>point is. He is an outstanding C programmer on heavy duty hardware. I
>suspect his main concern is what is about to happen as multi-core CPUs
>become the norm on the desk-top. Are we going to have to learn the hard
>lessons (learnt on main-frames) all over again.
>
>Please try to take his concerns seriously even if understanding them
>proves difficult (but the area he is considering is fraught with
>surprises and unexpected difficulties that many programmers are unable
>to grasp)

I do take them seriously, and if anything in my message indicated
otherwise, it certainly wasn't intentional, and I apologize if I made
that impression. I also missed the wrong section number of the
standard (1.9 instead of 1.7), as others have pointed out.

Still, it is hard to understand exactly what his concerns are without
some specific example, even if it is written in C and not C++. We know
that C++ currently lacks complete standardization in the area of
multi-threaded programming. However, not knowing that C++ exceptions
and hardware exceptions are not the same didn't go very far to ease my
doubts that he might also have some misconceptions about what a
sequence point is.

As you say, it is a difficult thing for most programmers to understand
completely (including myself, of course). It would be great if we
could learn something here from a "non-C++ programmer". :)

--
Bob Hairgrove
NoSpamPlease@Home.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Sean Kelly" <sean@f4.ca>
Date: Thu, 26 Jan 2006 11:12:16 CST
Raw View
Bob Hairgrove wrote:
> On Wed, 25 Jan 2006 20:02:46 GMT, nmm1@cus.cam.ac.uk (Nick Maclaren)
> wrote:
> >
> >This means that there is no sequence-point ordering between the
> >update of A and the execution of the handler, and the programmer has
> >no power to introduce any.  So what happens when the handler completes?
> >Well, in practice, nothing special.  This is BAD news.  As far as the
> >standard goes, that is pretty hard to justify, but I can assure you
> >that it is what implementations will do.

Why is this a problem?  I would assume that in a multithreaded
environment, the exception would be handled by the same thread that
threw it, so memory synchronization should not be an issue in this
case.  Is this incorrect?

> >What almost all systems do, after the problem has bit badly enough
> >that they realise it must be fixed, is to put a barrier somewhere
> >around the throw/catch (it doesn't matter where).  Unless I have
> >missed something critical, C++ needs to do the same.
>
> Can you please give us a concrete example? What kind of barrier are
> you referring to?

I think he's referring to a memory barrier, which are used to order
memory accesses on SMP systems.

> >So I am right or confused?
>
> You are probably confused as to what a sequence point is ... lots of
> C++ programmers are, and since you aren't a C++ programmer, it
> wouldn't be surprising.

This seems like a gray area at the moment.  Since the current spec
assumes a single-threaded model, I don't believe there is any language
describing what a sequence point means in memory synchronization terms.
 I'm not sure I entirely understand the issue Nick is describing, but I
do believe it is indicative of questions that may arise as C++ 0x
memory model talk continues.


Sean

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Martin Bonner" <martinfrompi@yahoo.co.uk>
Date: Thu, 26 Jan 2006 12:13:15 CST
Raw View
Nick Maclaren wrote:
> Please do NOT answer it unless you have a more than casual knowledge
> of the sequence point morass,

Sorry, I'm going to ignore that request.

> The simplest example is a function that evaluates an expression that
> updates an object A while (not sequence-point separated) throwing an
> exception B.

Somebody asked for example of such a piece of code.  Does the following
qualify?

int global;
void g(int,int);
void f(int i)
{
    g( global++, (i > 0?i:throw "negative i") );
}

> So I am right or confused?
or both?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: invalid@bigfoot.com (Bob Hairgrove)
Date: Thu, 26 Jan 2006 18:23:25 GMT
Raw View
On Thu, 26 Jan 2006 17:12:31 GMT, nmm1@cus.cam.ac.uk (Nick Maclaren)
wrote:

>|> Your simplest example could be made even more simple if you could
>|> provide us with a piece of code. Without code it's very difficult (for
>|> me, at least) to visualize the problem and follow you. In particular, I
>|> cannot figure out how an expression could update an object and throw
>|> without a separating sequence-point. I'm sure you have an example ready,
>|> could you post it, please?
>
>    (a = 0) + (b + c);
>
>Where b+c overflows.  There are more complex examples that can occur
>even in the cleanest of code.

Section 5, paragraph 5 says that the above is undefined behavior if
(b+c) overflows. The standard says nothing about hardware interrupts
or exceptions here. The assumption is that there would be a sequence
point at the semicolon.

Furthermore, the order of execution is unspecified:  i.e., (a=0) can
be performed either before or after (b+c) on a conforming
implementation, and if (b+c) doesn't overflow, the result is
well-defined either way. And if it does overflow, the result is
undefined, also either way.

Note that if b and c are unsigned integers, there is no overflow, but
the value wraps (check out section 3.9.1, paragraph 4, footnote 41),
thus the expression's value is also well-defined. However, the same
caveats WRT hardware exceptions or signals apply.

--
Bob Hairgrove
NoSpamPlease@Home.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Thu, 26 Jan 2006 13:06:04 CST
Raw View
Nick Maclaren wrote:
>     (a = 0) + (b + c);
> Where b+c overflows.

C++ exceptions are unrelated to overflow or other
such problems. If b+c overflows, then the code has
officially encountered undefined behavior, and the
standard says nothing at all about what this program
must do.

Some implementations do choose to convert such events
into C++ exceptions (and that's fine, since vendors
may choose to define undefined behavior for themselves),
and then you need to consult with them as to what they
have done for this case.

I would expect, given the lack of a sequence point here,
that you can make no assumptions about the state of 'a'
once the converted hardware exception is thrown.

I have argued elsewhere that the entire concept of
sequence points is misguided, and should be abandoned in
favor of a strict left-to-right order of evaluation with
side-effects happening as they are encountered. If this
were to be adopted, then you could be assured that a was
set when b + c threw. I'm glad you've added another point
in favor of my argument :-)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Thu, 26 Jan 2006 19:22:07 GMT
Raw View
In article <1138267396.096337.220330@g47g2000cwa.googlegroups.com>,
Sean Kelly <sean@f4.ca> wrote:
>
>Why is this a problem?  I would assume that in a multithreaded
>environment, the exception would be handled by the same thread that
>threw it, so memory synchronization should not be an issue in this
>case.  Is this incorrect?

Yes, on several grounds.  Let's stick to the same thread, as it seems
that asynchronous exceptions are not something that C++ programmers
will swallow.

C/C++ sequence points apply only to one thread (obviously) and are
about memory synchronization.  In particular, a compiler is allowed
to move memory operations around arbitrarily between them (subject
to doing the right operations), and is allowed to permit them to
be delayed by the hardware.

If there is no sequence point between the 'failing' function and the
handler, then the handler must not access anything accessed by the
failing function.  Well, actually, if the handler is rooted at the
location of the catch, it must not access anything accessed in the
controlled clause.  BAD news.

And it can get worse ....

>> >What almost all systems do, after the problem has bit badly enough
>> >that they realise it must be fixed, is to put a barrier somewhere
>> >around the throw/catch (it doesn't matter where).  Unless I have
>> >missed something critical, C++ needs to do the same.
>>
>> Can you please give us a concrete example? What kind of barrier are
>> you referring to?
>
>I think he's referring to a memory barrier, which are used to order
>memory accesses on SMP systems.

Not necessarily.  The problems arise just as badly on serial systems
with loose memory ordering models, like the Alpha.  If you look at
the code of pretty well any FLIH (first level interrupt handler),
you will see such things.  Or, for the really ancient, the Model 91
pipeline drain instructions in IBM's Mod II Fortran library :-)

>This seems like a gray area at the moment.  Since the current spec
>assumes a single-threaded model, I don't believe there is any language
>describing what a sequence point means in memory synchronization terms.

It doesn't even make sense on a SERIAL system :-(

> I'm not sure I entirely understand the issue Nick is describing, but I
>do believe it is indicative of questions that may arise as C++ 0x
>memory model talk continues.

Spot on.  And that is what I am involved with.


Regards,
Nick Maclaren,
University of Cambridge Computing Service,
New Museums Site, Pembroke Street, Cambridge CB2 3QH, England.
Email:  nmm1@cam.ac.uk
Tel.:  +44 1223 334761    Fax:  +44 1223 334679

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: invalid@bigfoot.com (Bob Hairgrove)
Date: Thu, 26 Jan 2006 19:23:44 GMT
Raw View
On Thu, 26 Jan 2006 12:13:15 CST, "Martin Bonner"
<martinfrompi@yahoo.co.uk> wrote:

>Somebody asked for example of such a piece of code.  Does the following
>qualify?
>
>int global;
>void g(int,int);
>void f(int i)
>{
>    g( global++, (i > 0?i:throw "negative i") );
>}

Did you perhaps mean ++global ... ???

--
Bob Hairgrove
NoSpamPlease@Home.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Thu, 26 Jan 2006 13:47:54 CST
Raw View
In article <it5it1pe79fcqe7sj8vphf63tmgq293nuu@4ax.com>,
Bob Hairgrove <invalid@bigfoot.com> wrote:
>On Thu, 26 Jan 2006 12:13:15 CST, "Martin Bonner"
><martinfrompi@yahoo.co.uk> wrote:
>
>>Somebody asked for example of such a piece of code.  Does the following
>>qualify?
>>
>>int global;
>>void g(int,int);
>>void f(int i)
>>{
>>    g( global++, (i > 0?i:throw "negative i") );
>>}
>
>Did you perhaps mean ++global ... ???

It makes no difference.  Either update global, and there is no ordering
specified between that update and the conditional expression that
contains the throw.

Now, see my previous lecture about the different sequence point models
for the possible viewpoints on whether the sequence point at the '?'
is relevant or not :-)

To the other posters:  please don't confuse the issue.  I gave an example,
and kept it simple in order to keep it clear.  Yes, I know all that,
but please see 1.9 paragraph 15 for where the standard says that this
may raise an exception.  If you prefer, use the following:

    (a = 0) + (throw "balls");

Yes, sequence points are fundamentally broken, but the simple solution
of lexicographic ordering conflicts with many forms of optimisation.
Simple left-to-right won't work, because of:

    a = fred(a = 1);


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Sean Kelly" <sean@f4.ca>
Date: Thu, 26 Jan 2006 16:52:58 CST
Raw View
Nick Maclaren wrote:
> In article <1138267396.096337.220330@g47g2000cwa.googlegroups.com>,
> Sean Kelly <sean@f4.ca> wrote:
> >
> >Why is this a problem?  I would assume that in a multithreaded
> >environment, the exception would be handled by the same thread that
> >threw it, so memory synchronization should not be an issue in this
> >case.  Is this incorrect?
>
> Yes, on several grounds.  Let's stick to the same thread, as it seems
> that asynchronous exceptions are not something that C++ programmers
> will swallow.
>
> C/C++ sequence points apply only to one thread (obviously) and are
> about memory synchronization.  In particular, a compiler is allowed
> to move memory operations around arbitrarily between them (subject
> to doing the right operations), and is allowed to permit them to
> be delayed by the hardware.
>
> If there is no sequence point between the 'failing' function and the
> handler, then the handler must not access anything accessed by the
> failing function.  Well, actually, if the handler is rooted at the
> location of the catch, it must not access anything accessed in the
> controlled clause.  BAD news.

15.1.3 contains language that may indicate the presence of a sequence
point during the evaluation of the throw-expression:

"A throw-expression initializes a temporary object. . . [which] is used
to initialize
the variable named in the matching handler (15.3). . .  Except for
these restrictions and the restrictions on type matching mentioned in
15.3, the operand of throw is treated exactly as a function argument in
a call (5.2.2) or the operand of a return statement."

Since at least one initialization is taking place here, surely a
sequence point must implicitly exist before the handler is evaluated.
And if not, perhaps the language could be strengthened to require one?
This seems like it may have more overall utility than trying to address
the issue in 1.9.17.

> >I think he's referring to a memory barrier, which are used to order
> >memory accesses on SMP systems.
>
> Not necessarily.  The problems arise just as badly on serial systems
> with loose memory ordering models, like the Alpha.  If you look at
> the code of pretty well any FLIH (first level interrupt handler),
> you will see such things.  Or, for the really ancient, the Model 91
> pipeline drain instructions in IBM's Mod II Fortran library :-)

Seriously?  I was under the impression that accesses would always be
observed in program order by the issuing processor.  I would think that
anything weaker in this respect would be extremely difficult to program
for.  Though I suppose this particular topic is better suited for a
different forum.

> >This seems like a gray area at the moment.  Since the current spec
> >assumes a single-threaded model, I don't believe there is any language
> >describing what a sequence point means in memory synchronization terms.
>
> It doesn't even make sense on a SERIAL system :-(

It sounds like this memory model work is going to be even more fun than
I'd imagined ;-)  I do hope that something reasonable can be worked
out.


Sean

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com
Date: Thu, 26 Jan 2006 18:32:24 CST
Raw View
Nick Maclaren wrote:

> The simplest example is a function that evaluates an expression that
> updates an object A while (not sequence-point separated) throwing an
> exception B.  One interpretation of sequence points is that the handler
> is then called in a nonce sub-tree rooted at the point the exception
> is raised.  That is, actually, how a large proportion of hardware
> works.
>
> This means that there is no sequence-point ordering between the
> update of A and the execution of the handler, and the programmer has
> no power to introduce any.

In C++ there is a sequence point at the completion of the evaluation of
each full-expression (1.9/16).  Throwing an exception is just another
way to end the evaluation of a full-expression (12.2/3).  Therefore,
there is always a sequence point between the evaulation of a
throw-expression and entry into the corresponding catch block.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Fri, 27 Jan 2006 01:56:08 CST
Raw View
Nick Maclaren wrote:
> please don't confuse the issue

Fundamentally, once you are in the exception handler,
yo have encountered the end-of-full-expression sequence
point, and the end-of-function sequence point if the
handler is in a different function. All side effects are
therefore settled. So there isn't any problem in the C++
standard related to this.

> Yes, sequence points are fundamentally broken, but the simple solution
> of lexicographic ordering conflicts with many forms of optimisation.

We've been through this argument here many times, and no one
has presented any convincing evidence that this is the case.

> Simple left-to-right won't work, because of:
>     a = fred(a = 1);

The rule is (should be, rather)that in an expression, operands
are evaluated left to right, then the operation is done. Function
call expressions evaluate the function to be called, bind each
argument to its parameter left-to-right, then call the function.
Lvalue expressions bind to their object when evaluated. In your
example, we have
     = a
       fred
           =
           a
           1
So first we evaluate lvalue a, then 'fred(a = 1)'. For the latter,
we evaluate fred, then evaluate the argument expression. For that,
we evaluate lvalue a, then 1, then perform the assignment. Then we
bind the result (generally a itself) to fred's first parameter. Then
we call fred, and assign the result to a.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Fri, 27 Jan 2006 01:56:16 CST
Raw View
In article <GRZBf.38002$dW3.34874@newssvr21.news.prodigy.com>,
John Nagle <nagle@animats.com> writes:
|>
|>     There are two basic problems worth thinking about here,
|> and it's not clear which one the original poster is trying
|> to discuss.
|>
|>     First, there's the problem of converting a machine exception
|> into a C++ exception.  C++, unlike Ada, does not generally
|> support that at all, although some implementations do try to
|> make it work.  But that's a language extension.
|>
|>     Second, there's the issue of what happens when the compiler
|> has introduced paralleism into a sequential program as an
|> optimization, and one of the threads throws an exception.
|> The original poster seems to be trying to figure out just
|> how conservative the C++ standard requires the compiler to be
|> in such situations.  SGI has (had?) compilers which did such
|> optimizations, and so this problem is more than theoretical.

Nope.  There is a third.  Precisely what does the standard assume,
intend, require and forbid, is its wording complete, does it
express the intent, is it usable for its purpose and is it
implementable on important systems.  And it is THAT which I am
trying to track down, and extend.

That level constrains the two you mentioned.


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Fri, 27 Jan 2006 07:55:30 GMT
Raw View
In article <a7nft115m54bime7trdj1u6v3u7abb615m@4ax.com>,
invalid@bigfoot.com (Bob Hairgrove) writes:
|>
|> As you say, there is a sequence point after the copying of a return
|> value. However, that doesn't mean that there are no sequence points
|> before that. 1.7 paragraph 11 tells you what a sequence point is ...
|> is that what you think it is?

Grrk.  Unfortunately, my confusion about sequence points is several
levels deeper than that.  I need to go into lecture mode, I am
afraid.

Sequence points are defined as locations in the parse tree, with
semantic synchronisation effects in the abstract (execution) machine.
Unfortunately, C90 (and hence C++) never defined the precise model
used for the abstract machine, and sequence points use wording that
appears to make sense on its own, but doesn't, once you start to
work out exactly what it implies about the ordering of the parse
tree.  There are three interpretations that I know of that are
compatible with the wording in the standards, and are established
in computer language and compiler technology:

1) The wording applies to the lexicographic order.  This would mean
that (a = 0) + (0 , 1) + (a = 1) is defined.  This is generally
regarded as so insane that nobody could have meant it.

2) The wording means that the sequence points define basic blocks,
which are executed in an order defined by other constraints.  This
is generally regarded as little saner.

3) The generally accepted one takes the DAG that is derived from the
parse tree, and uses that to give the partial order that is used
when the words 'previous' and 'subsequent' are used.  Note that it
is a generalised DAG, in that nodes can be attached 'together' as
well as in a particular order.  Now, there are three variations of
THIS :-(

    a) All paths from A to B go through at least one sequence
point.  This is generally not favoured, though a few people held
on to it when we last discussed it.

    b) A is sequenced before B only if that is required by the
sequence point rules as applied to the DAG.  This is the hard line.

    c) A is sequenced before B if that is required by the sequence
point rules as applied to the DAG together with other standard
specified constraints.

Now, there are more contorted interpretations that are compatible
with the wording, but the above should be enough for now.  Most
people have serious trouble getting their minds round even the
above.

|> If you are not a C++ programmer, please explain how have you been
|> "caught by it" and how this relates to the C++ language?

Three reasons:

It's a language-independent problem.  C++ uses a similar semantic
model to C90, which I have used extensively.  And, despite its
numerous idiosyncracies, C++ is a fairly run of the mill third
generation programming language with brass knobs on.

|> What is a "nonce sub-tree"?

One created for that specific action.

|> Can you please give us a concrete example? What kind of barrier are
|> you referring to?

A constraint in the standard - e.g. a sequence point.

|> Since you are not a C++ programmer, you probably aren't aware that C++
|> exceptions and hardware exceptions are not the same thing. ...

Yes, I am fully aware of that.

|> You are probably confused as to what a sequence point is ... lots of
|> C++ programmers are, and since you aren't a C++ programmer, it
|> wouldn't be surprising.

True, I am.  But, now you have read my lecture, have I confused
you as well?  If not, I have failed to explain the issue properly.


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Fri, 27 Jan 2006 07:55:27 GMT
Raw View
John Nagle ha scritto:
>     inline float f(float x)
>     {    ...    } // expensive function with no side effects
>             // but which could raise an exception
>
>     float tab1[100000];
>     float tab2[100000];
>     ...
>     for (int i=0; i<100000; i++)
>     {    tab2[i] = f(tab1[i]);    }
>
> A multithreading compiler might try to optimize this by putting
> a few threads to work on the problem.  That works out fine,
> unless some call to f raises an exception.  Should that occur,
> "tab1" would be partially updated, but the updated elements
> might not necessarily be sequential.

Are you sure such optimization is allowed? Wouldn't that violate 1.9/8?

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: krixel@qed.pl ("Krzysztof elechowski")
Date: Fri, 27 Jan 2006 08:11:46 GMT
Raw View
U=BFytkownik "Nick Maclaren" <nmm1@cus.cam.ac.uk> napisa=B3 w wiadomo=B6c=
i=20
news:drb8r5$9ll$1@gemini.csx.cam.ac.uk...
>> Yes, sequence points are fundamentally broken, but the simple solution
> of lexicographic ordering conflicts with many forms of optimisation.
> Simple left-to-right won't work, because of:
>
>    a =3D fred(a =3D 1);
>

What is wrong with that?  Read left to right, Motorola mnemonics, argumen=
ts=20
passed on stack, non-optimized:

LEA a, A0    ; for a =3D fred
MOVEM A0,-(A7)    ; save A0
LEA a, A0    ; for a =3D 1
MOVEQ #1, D0
MOVE D0,(A0)
MOVE A0,-(A7)    ; call fred by reference
JSR fred
MOVE (A7)+,D0
MOVEM (A7)+, A0    ; restore A0
MOVE D0,(A0)

What is not simple in the code above?
Chris


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Fri, 27 Jan 2006 08:11:31 GMT
Raw View
nmm1@cus.cam.ac.uk (Nick Maclaren) writes:

> Well, I sent this to a couple of relevent reflectors, and got a
> stunned silence - not a new experience for me - so let's see if
> this newsgroup does better :-)
>
> Please do NOT answer it unless you have a more than casual knowledge
> of the sequence point morass, as I can assure you that it is an evil
> question.  The same applies to the implementation aspects.  But note
> that I am not a C++ programmer!
>
> Well, either I have seriously omitted to find the reference, or there
> is a serious omission in the C++ standard :-)
>
> 1.7 paragraph 17 says that there is a sequence point after the copying
> of a return value and before the execution of any expressions outside
> the function, and the attached footnote makes it clear that this was
> intended to apply to throw and catch.
>
> Er, but isn't the point about throwing an exception within a function
> and catching it by a handler outside that function (perhaps in a quite
> different file) that the return value is NOT copied?

Yes.  But you don't need exceptions to get to this point.  Consider
functions that return void.  No return value is copied there, either.
The fact that this isn't covered explicitly smells like a defect to
me.

> Now, for reasons that I can attempt to explain in ghastly detail (but
> will almost certainly not make clear), this is a real problem and is
> much more serious for threaded codes than for serial ones.  I have
> been caught by it, badly, on some systems.

I don't think sequence points make any particular difference to
threaded code.

> The simplest example is a function that evaluates an expression that
> updates an object A while (not sequence-point separated) throwing an
> exception B.  One interpretation of sequence points is that the handler
> is then called in a nonce sub-tree

I think you'd better define that.

> rooted at the point the exception is raised.  That is, actually, how
> a large proportion of hardware works.
>
> This means that there is no sequence-point ordering between the
> update of A and the execution of the handler, and the programmer has
> no power to introduce any.  So what happens when the handler completes?
> Well, in practice, nothing special.  This is BAD news.

What do you expect to happen when the handler completes?
What would be better news?

> As far as the standard goes, that is pretty hard to justify,

How so?

> but I can assure you that it is what implementations will do.
>
> What almost all systems do, after the problem has bit badly enough
> that they realise it must be fixed, is to put a barrier somewhere
> around the throw/catch (it doesn't matter where).  Unless I have
> missed something critical, C++ needs to do the same.

I don't think C++ needs to do anything with barriers until we
introduce a concurrency model.  The current guarantees given by
sequence points are good enough for the programming model we have
today, once the hole in 1.7/17 is closed, that is.

> Note that this ISN'T just relevant to hardware-raised exceptions,

Good, because those have no particular relationship to the C++
language or what C++ calls an exception.

> but applies even to the most explicit throw/catch clauses on systems
> with sufficiently loose memory ordering.
>
> So I am right or confused?

Maybe both :)

It's clear that your background and vocabulary differs from what most
of us are used to, so if you could give a minimal example of the sort
of thing you're talking about, I think it would make things lots
clearer.  It always helps to look at code.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Fri, 27 Jan 2006 08:11:57 GMT
Raw View
hyrosen@mail.com (Hyman Rosen) writes:

> Nick Maclaren wrote:
>> 1.7 paragraph 17 says that there is a sequence point after the copying
>> of a return value and before the execution of any expressions outside
>> the function
>
> That's 1.9/17, not 1.7/17.
>
>> Er, but isn't the point about throwing an exception within a function
>> and catching it by a handler outside that function (perhaps in a quite
>> different file) that the return value is NOT copied?
>
> So? 1.9/17 tells you that there are two sequence points.
> There's one after copying the returned value, and there's
> one before execution of any expressions outside the
> function. Since the exception handler is most assuredly
> outside the function, a sequence point has been encountered
> before the handler receives control.

Ah!  I should've read on before posting.  No hole in 1.7/17 (or 1.9/17
either).

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Sean Kelly" <sean@f4.ca>
Date: Fri, 27 Jan 2006 14:27:47 CST
Raw View
Alberto Ganesh Barbati wrote:
> John Nagle ha scritto:
> >     inline float f(float x)
> >     {    ...    } // expensive function with no side effects
> >             // but which could raise an exception
> >
> >     float tab1[100000];
> >     float tab2[100000];
> >     ...
> >     for (int i=0; i<100000; i++)
> >     {    tab2[i] = f(tab1[i]);    }
> >
> > A multithreading compiler might try to optimize this by putting
> > a few threads to work on the problem.  That works out fine,
> > unless some call to f raises an exception.  Should that occur,
> > "tab1" would be partially updated, but the updated elements
> > might not necessarily be sequential.
>
> Are you sure such optimization is allowed? Wouldn't that violate 1.9/8?

The "as if" rule is the important factor here, though it was perhaps a
bad example to include a function call.  More generally:

    float tab1[100000];
    float tab2[100000];

    for (int i=0; i<100000; ++i) {
        tab1[i] += tab2[i];
    }

could be unrolled to:

    float tab1[100000];
    float tab2[100000];

    parbegin;
        addAssignRange( &tab1[0], &tab2[0], 100 );
        addAssignRange( &tab1[100], &tab2[100], 100 );
        ...
    parend;

where all statements inside of 'parbegin' and 'parend' are executed in
parallel and parend waits for all operations to complete before
continuing.  In this case, 'parend' is effectively a fancy sequence
point.  To handle exceptions, I suppose an implementor might have
'parend' check the completion status of each task and raise an
exception in the calling thread if any of them failed.  The drawback
here is that while this approach sidesteps the problem of memory
synchronization, it has to deal with the possibility that more than one
task may have failed, and they may have failed for different reasons.


Sean

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: wkaras@yahoo.com
Date: Fri, 27 Jan 2006 16:21:35 CST
Raw View
Nick Maclaren wrote:
.
> Yes, sequence points are fundamentally broken, but the simple solution
> of lexicographic ordering conflicts with many forms of optimisation.
.

Depends on what definition of "broken" you use, your's or the
Standards.  The Standard defines observable events
(calls to library functions, volatile accesses).  It defines
how to determine the sequence of sequence points
for a program, and how to determine the pair of
adjacent sequence points an observable event
falls between.  If two observable events fall between
the same pair of sequence points, it's arbitrary
which event occurs first.  In this case, it's simply
the responsibility of the programmer (if it matters
which event occurs first) to insert a sequence point
between the two events.  If you could find some
sequence of observable events, and no one was
able to write a program with sequence points that
guaranteed the given sequence of events, this
would be evidence that the Standard is not meeting
its goals in this area.

I think what you want is a programming language
where the sequence of observable events is
fully defined in any valid program.  But, as you
point out, that makes optimization more
difficult.  C and C++ made a trade-off in
favor of making optimization easier.

Side topic:  In the expression "foo(),  bar()",
doesn't the Standard allow foo and bar
to be excuted in parallel?  I'm not sure
the Standard really lacks support for
multithreading.  I just lacks support for
the inter-thread communication
mechanisms that we're used to.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Sean Kelly" <sean@f4.ca>
Date: Fri, 27 Jan 2006 16:23:38 CST
Raw View
Hyman Rosen wrote:
> Nick Maclaren wrote:
>
> > Er, but isn't the point about throwing an exception within a function
> > and catching it by a handler outside that function (perhaps in a quite
> > different file) that the return value is NOT copied?
>
> So? 1.9/17 tells you that there are two sequence points.
> There's one after copying the returned value, and there's
> one before execution of any expressions outside the
> function. Since the exception handler is most assuredly
> outside the function, a sequence point has been encountered
> before the handler receives control.

My reading of 1.9.17 is somewhat different.  The first section is
clear:

"There is a sequence point after the evaluation of all function
arguments (if any) which takes place before execution of any
expressions or statements in the function body."

So there will always be a sequence point on function entry, whether or
not there are any arguments.  But I only see mention of a single
sequence point in the second section:

"There is also a sequence point after the copying of a returned value
and before the execution of any expressions outside the function."

To me, this desribes a single sequence point located between the return
value copy operation and the execution of the first expression
following the function call (the singular terms "is" and "a" are
indicative here).  Also, some functions no not return a value--is a
sequence point present in this case?  And what if an exception is
thrown from a dtor, which would occur after the return value is copied?

The remainder of the section merely mentions that some things which do
not look like function calls from a lexical standpoint (such as "new")
may actually be treated as function calls from an implementation
standpoint.  I'd actually hoped that this clause would be the saving
grace for "throw," but it's not entirely clear whether raising an
exception necessarily involves the presense of a sequence point or not.
 My ultimate conclusion is that it does, given that the process
requires the completed construction of at lesat one temporary value,
but there may still be room for interpretation.

If the intent is different than what I have outlined above, then I
would suggest that the language be reworded to make this more obvious,
if only because Nick's confusion suggests that the current language is
unclear.  Particularly since this clause may be significant if C++
comes to include some support for concurrency.


Sean

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Fri, 27 Jan 2006 16:49:47 CST
Raw View
Nick Maclaren wrote:
> If there is no sequence point between the 'failing'
 > function and the handler

Fortunately there is, as 1.9/17 points out
     "There is also a sequence point after the copying
      of a returned value and before the execution of
      any expressions outside the function."

The fact that there is no returned value copied in some
cases is irrelevant. After all, we have void functions
which never copy a value. The exception handler is not
within the function, and furthermore, once the throw
expression is evaluated, the complete expression it's in
is done, and so that sequence point kicks in, as you can
see from 12.2/3:
     "Temporary objects are destroyed as the last step in
      evaluating the full-expression (1.9) that (lexically)
      contains the point where they were created. This is
      true even if that evaluation ends in throwing an
      exception."

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nmm1@cus.cam.ac.uk (Nick Maclaren)
Date: Fri, 27 Jan 2006 17:48:02 CST
Raw View
In article <1138388581.784109.150920@g14g2000cwa.googlegroups.com>,
Sean Kelly <sean@f4.ca> wrote:
>
>My reading of 1.9.17 is somewhat different.  ...

Precisely.

>If the intent is different than what I have outlined above, then I
>would suggest that the language be reworded to make this more obvious,
>if only because Nick's confusion suggests that the current language is
>unclear.  Particularly since this clause may be significant if C++
>comes to include some support for concurrency.

Quite, but I don't think that you realise the depth of the morass.
When this was debated on the SC22WG11 reflector, there were at
least three viewpoints that were held to be the absolutely clear
intent - and all three were incompatible.  And there were at least
another five that were put forward as possible interpretations.
To my certain knowledge, at least three of those variations have
been used by different compilers.

My confusion is because I understand the issue, not because I don't.
The fact of the matter is that the wording and semantics of sequence
points is in terms of a sequence of actions, but the syntax of the
language is in terms of a tree (with no natural ordering between the
branches leaving a given node).  That is mathematical nonsense.

I shall stop here, but I thank everyone for pointing out aspects of
C++ sequencing that I did not realise existed.  Let us hope that
Hans Boehm's attempts to get a memory model for threading produce
something with a little more consensus than sequence points have.


Regards,
Nick Maclaren.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hyrosen@mail.com (Hyman Rosen)
Date: Sat, 28 Jan 2006 07:15:06 GMT
Raw View
Nick Maclaren wrote:
> But, now you have read my lecture, have I confused you as well?
 > If not, I have failed to explain the issue properly.

I'm still confused by your confusion, as the description in the
C++ standard seems reasonably straightforward to me as far as
the issues you're asking about.

If you look at 1.9/3, 1.9/5, and 5/4, you'll see that C++ has
cases of unspecified execution order. The program is required
to act as if it has picked one allowed order, and the program
has undefined behavior if any allowed order has undefined
behavior. Throwing an exception within an expression causes it
to be complete, and therefore there is a sequence point after
such a throw. Multithreading is not addressed by the standard,
so questions regarding that must be addressed to the vendor
(although we can discuss different possible implementations).
Converting hardware exceptions to C++ exceptions is similarly
not covered by the standard, so again vendors must be consulted.

Do you still have bits of example code where you do not know
what is required by C++? Has the case of
     f(a = 1, b ? 0 : throw 1);
has been explained to your satisfaction?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Sat, 28 Jan 2006 07:15:42 GMT
Raw View
Sean Kelly ha scritto:
> Alberto Ganesh Barbati wrote:
>> John Nagle ha scritto:
>>>     inline float f(float x)
>>>     {    ...    } // expensive function with no side effects
>>>             // but which could raise an exception
>>>
>>>     float tab1[100000];
>>>     float tab2[100000];
>>>     ...
>>>     for (int i=0; i<100000; i++)
>>>     {    tab2[i] = f(tab1[i]);    }
>>>
>>> A multithreading compiler might try to optimize this by putting
>>> a few threads to work on the problem.  That works out fine,
>>> unless some call to f raises an exception.  Should that occur,
>>> "tab1" would be partially updated, but the updated elements
>>> might not necessarily be sequential.
>> Are you sure such optimization is allowed? Wouldn't that violate 1.9/8?
>
> The "as if" rule is the important factor here, though it was perhaps a
> bad example to include a function call.  More generally:
>
>     float tab1[100000];
>     float tab2[100000];
>
>     for (int i=0; i<100000; ++i) {
>         tab1[i] += tab2[i];
>     }
>
> could be unrolled to:
>
>     <snip>
>

In your example (i.e.: without a function call) where only PODs are
involved, there's nothing that can throw a C++ exception. So, from the
C++ point of view, there is *no* exception to handle (non-C++ exceptions
invokes UB so there's no point in considering them). I agree that in
this case the "as if" rule allows parallelization because there can be
no difference in the observed behavior.

However, when a function call is involved 1.9/8 is very clear IMHO in
forbidding this kind of optimization. The same would happen if you used
UTDs instead of PODs because assignment would be implemented through a
function call.

An example that is may be more interesting might be the following:

  for (int i=0; i<100000; ++i) {
    tab1[i] += (tab2[i] > 0 ? tab[2] : throw 1);
  }

whether this loop is allowed to be parallelized or not, frankly I don't
know. For sure, in case an exception is thrown, the observed behavior
would be different from the single-thread case... the "as if" rule would
therefore suggest that parallelization can't be used.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Ben Hutchings <ben-public-nospam@decadentplace.org.uk>
Date: Sun, 29 Jan 2006 17:49:51 CST
Raw View
Hyman Rosen <hyrosen@mail.com> wrote:
> Nick Maclaren wrote:
>> But, now you have read my lecture, have I confused you as well?
> > If not, I have failed to explain the issue properly.
>
> I'm still confused by your confusion, as the description in the
> C++ standard seems reasonably straightforward to me as far as
> the issues you're asking about.

It's possible that you're assuming "obvious" meanings for some
terminology that isn't really precise enough.

> If you look at 1.9/3, 1.9/5, and 5/4, you'll see that C++ has
> cases of unspecified execution order. The program is required
> to act as if it has picked one allowed order, and the program
> has undefined behavior if any allowed order has undefined
> behavior.

It's clear from 5/4 that sequence points provide a partial ordering of
operations in expression evaluation.  It implies that there's a total
ordering of the sequence points themselves, but what provides this
ordering?  I think that's what Nick was getting at.

Would you say that

    int a = 0;
    (0, a += 1, 0) + (0, a += 2, 0);

has defined behaviour?  Do you think this is entirely clear from
the current standard?

I, and I think Nick, think "sequence points" should be replaced by
explicit definition of a partial ordering of operations.

> Throwing an exception within an expression causes it
> to be complete, and therefore there is a sequence point after
> such a throw.

Please justify this statement with reference to the standard.

> Multithreading is not addressed by the standard,
> so questions regarding that must be addressed to the vendor
> (although we can discuss different possible implementations).
<snip>

Nick's involved in the effort to standardise a multithreaded memory
model.  It is helpful for us to clarify the starting point before
making changes.

Ben.

--
Ben Hutchings
Computers are not intelligent. They only think they are.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]