Topic: exceptions specification improvement


Author: "Michael Pryhodko" <mpryhodko@westpac.com.au>
Date: Thu, 17 Nov 2005 18:09:44 CST
Raw View
Hi

AFAIK, "exception specifications" in it's current state are considered
flawed and unusable. So far most people prefer to use 'throw()' to mark
no-throw functions or not use throw-spec at all. I am risking to
reinvent wheels again, but recently I have got an idea how this could
be improved.

Generated code should behave in fashion similar to code generated by
VC7.1, i.e:
1. 'unexpected_handler' and  'bad_exception' are not used
2. compiler in its optimization schemes uses only 'no-throw'/'throw'
fact. That means if function throws exception which is not listed in
throw-spec -- it will propagate further normally, but application state
can be possibly corrupted (it depends on compiler, for example in case
of no-throw function compiler could generate code more efficient code
around call to this function, but which is not prepared for exceptions
from it), i.e. 'program is ill-formed'.
3. Compiler should have options to 'switch off' certain exceptions. For
example:
cpp.exe -no_exc 'std::bad_alloc'

results in:
- all 'std::bad_alloc' and it's descendant are implicitly removed in
every throw-spec used by translation unit
- all calls 'throw X', where X is std::bad_alloc or it's descendant,
are replaced by 'abort()'.

4. template functions will get throw-spec on instantiation stage and
will be treated as normal functions afterwards -- in this way good
library (e.g. STL) could be used in most reliable apps and in most
unreliable -- with the same code base you will get safety and speed
(for unreliable apps).

Notes:
- this will result in more no-throw functions, which lead to faster
code, since compiler could employ more optimizations around call to
them
- from my experience majority of applications are not prepared for this
or that extremal case or deliberately written in this way (games, for
example games usually stops in any excpetional situation, i.e. no
resources) -- with this behavior they will not pay for what they are
not using anyway.
- if X has other ancestors, i.e. using multiple inheritance, we are in
trouble :-) -- this case should be reviewed more closely
- on link stage warning should be generated if units compiled with
different sets of '-no_exc' options are linked against each other.
- inter-dll calls are to be reviwed too...

Bye.
Sincerely yours, Michael.

---
[ 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: Simon Bone <sf.bone@btinternet.com>
Date: Fri, 18 Nov 2005 11:38:46 CST
Raw View
On Thu, 17 Nov 2005 18:09:44 -0600, Michael Pryhodko wrote:

> Hi
>
> AFAIK, "exception specifications" in it's current state are considered
> flawed and unusable. So far most people prefer to use 'throw()' to mark
> no-throw functions or not use throw-spec at all. I am risking to
> reinvent wheels again, but recently I have got an idea how this could
> be improved.
>
> Generated code should behave in fashion similar to code generated by
> VC7.1, i.e:
> 1. 'unexpected_handler' and  'bad_exception' are not used 2. compiler in
> its optimization schemes uses only 'no-throw'/'throw' fact. That means
> if function throws exception which is not listed in throw-spec -- it
> will propagate further normally, but application state can be possibly
> corrupted (it depends on compiler, for example in case of no-throw
> function compiler could generate code more efficient code around call to
> this function, but which is not prepared for exceptions from it), i.e.
> 'program is ill-formed'.

>From what I gather, you are proposing to change currently well-defined
behavior (although not portable behavior) into undefined behavior. And
you want to do so without requiring changes to existing source code- I
think that would be a retrograde step.

However, you are pointing to a real problem. Integrating code that uses
exception specs with code that doesn't often results in poor performance
because we have no way of saying to the compiler, "Trust me, in this
context the functions I call don't throw". That is the statement I'd like
to see added to the standard.

e.g.

int foo();

int bar() throw(){
_Pragma trust_my_exception_specification
return foo();
}

This would suppress the compiler generated try/catch equivalent code.


> 3. Compiler should have options to 'switch off'
> certain exceptions. For example:
> cpp.exe -no_exc 'std::bad_alloc'
>
> results in:
> - all 'std::bad_alloc' and it's descendant are implicitly removed in
> every throw-spec used by translation unit - all calls 'throw X', where X
> is std::bad_alloc or it's descendant, are replaced by 'abort()'.
>

I think this would be pretty hard to implement and test, and I can't
imagine using it in production code. There are already too many ways to
introduce subtle changes in behavior into release-optimized code. For
debugging purposes, it's already possible to set a breakpoint on throw
statements.

> 4. template functions will get throw-spec on instantiation stage and
> will be treated as normal functions afterwards -- in this way good
> library (e.g. STL) could be used in most reliable apps and in most
> unreliable -- with the same code base you will get safety and speed (for
> unreliable apps).
>

I don't understand that. So what if a function currently called in the
implementation of a template function throws/doesn't throw? The test
should be whether the concept model allows throwing an exception (which I
think it usually should). Template specialization can take care of what to
do if a significant optimization opportunity exists for some likely
set of instantiation arguments including where a no throw guarantee can be
of advantage.

Simon Bone

---
[ 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: "Bob Bell" <belvis@pacbell.net>
Date: Fri, 18 Nov 2005 14:55:22 CST
Raw View
Michael Pryhodko wrote:
> Hi
>
> AFAIK, "exception specifications" in it's current state are considered
> flawed and unusable. So far most people prefer to use 'throw()' to mark
> no-throw functions or not use throw-spec at all. I am risking to
> reinvent wheels again, but recently I have got an idea how this could
> be improved.
>
> Generated code should behave in fashion similar to code generated by
> VC7.1, i.e:

[snip details]

I must be missing something. You seem to be suggesting relaxing the
behavior of exception specifications to the point that they actually
don't seem to do anything useful except perhaps corrupt the state of
the executable, in exchange for... what? What is the benefit of this
change?

Note that the optimizations you speak of have as an upper limit the
efficiency of code that doesn't use exception specifications at all, so
if you're really interested in efficiency, don't use exception
specifications.

Bob

---
[ 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: "Michael Pryhodko" <mpryhodko@westpac.com.au>
Date: Sun, 20 Nov 2005 18:53:24 CST
Raw View
> From what I gather, you are proposing to change currently well-defined
> behavior (although not portable behavior) into undefined behavior. And
> you want to do so without requiring changes to existing source code- I
> think that would be a retrograde step.

I disagree. Basically I propose to:
1. remove std::bad_exception substitution if throw-spec is violated
2. introduce mechanism for switching off certain exceptions.
I do not think that existing source code will suffer as long as it was
not relying on std::bad_exception. I believe it will benefit.


> However, you are pointing to a real problem. Integrating code that uses
> exception specs with code that doesn't often results in poor performance
> because we have no way of saying to the compiler, "Trust me, in this
> context the functions I call don't throw". That is the statement I'd like
> to see added to the standard.
>
> e.g.
>
> int foo();
>
> int bar() throw(){
> _Pragma trust_my_exception_specification
> return foo();
> }
>
> This would suppress the compiler generated try/catch equivalent code.

You are reading my mind :-). I was thinking about these hints to
compiler -- they could help to refine performance further. For example:

vector<int> v;
v.reserve(v.size() + 1);
v.push_back(5); // this will not throw for sure

but I was thinking about syntax similar to:

throw_cast<new-spec>(foo()); // at that point compiler 'believes' that
foo has 'new-spec' exception specification, which is subset of foo's
original exception specification.


> > results in:
> > - all 'std::bad_alloc' and it's descendant are implicitly removed in
> > every throw-spec used by translation unit - all calls 'throw X', where X
> > is std::bad_alloc or it's descendant, are replaced by 'abort()'.
> >
>
> I think this would be pretty hard to implement and test, and I can't
> imagine using it in production code. There are already too many ways to
> introduce subtle changes in behavior into release-optimized code. For
> debugging purposes, it's already possible to set a breakpoint on throw
> statements.

For me it looks quite simple. Almost looks like as if it could be done
on pre-compile stage -- by simple substitutions (however I am not
really proficient with building compilers).


> > 4. template functions will get throw-spec on instantiation stage and
> > will be treated as normal functions afterwards -- in this way good
> > library (e.g. STL) could be used in most reliable apps and in most
> > unreliable -- with the same code base you will get safety and speed (for
> > unreliable apps).
>
> I don't understand that. So what if a function currently called in the
> implementation of a template function throws/doesn't throw? The test
> should be whether the concept model allows throwing an exception (which I
> think it usually should). Template specialization can take care of what to
> do if a significant optimization opportunity exists for some likely
> set of instantiation arguments including where a no throw guarantee can be
> of advantage.

Well, I wanted to say:
- during compilation of given translation unit...
- template function gets exception specification during template
instantiation...
- this exception specification is generated by compiler based on
exception-specs of operations used in given template function -- I
think it is so according to C++ standard
- and then to this implicitly generated throw-spec compiler applies
'-no_exc' options by removing 'switched off' exceptions.

For example, if you switch off std::bad_alloc (which could be done in
90% of commercial software, and actually it will improve it's
reliability by killing app before it corrupts itself) -- almost all
std::vector's members will get no-throw spec after instantiation (lets
not take element's type into account for now),

Bye.
Sincerely yours, Michael.

---
[ 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: "Michael Pryhodko" <mpryhodko@westpac.com.au>
Date: Sun, 20 Nov 2005 18:52:41 CST
Raw View
> [snip details]
>
> I must be missing something. You seem to be suggesting relaxing the
> behavior of exception specifications to the point that they actually
> don't seem to do anything useful except perhaps corrupt the state of
> the executable, in exchange for... what? What is the benefit of this
> change?

Have you ever seen any application that is actually prepared for
std::bad_exception in every point where it could happen? I haven't seen
any. Even more -- I've seen only a handful of apps that prepared for
all exceptions that could be thrown from functions with not empty
throw-spec (implicit or not).

What I am proposing is to relax exception specs to the point where it
does not create run-time overhead, but provide additional performance
benefits:
a) by adjusting no-throw spec (no std::bad_exception usage -- compiler
could use this)
b) you could 'sacrifice' reliability for speed -- and no corruption of
application state, please note it. The only 'gate' to corruption is
that whenever throw-spec is violated:
(currently): std::bad_exception is thrown
(proposal): exception is propagated as usual

In first case app state is likely to be corrupted, since nobody
prepares for 'f() throw()' to throw. In second case situtation is the
same + 'compiler may generate code which is not prepared'. I think
since you have violated exception-spec -- you are already got
unexpected behavior, that means app state is likely to be corrupted,
and I do not like to pay in run-time for exception substitution which
is not useful anyway.

About 'nothing useful'... Let me show you an example:
Suppose you are game developer, usually you do not care about
std::bad_alloc, or any other exceptional situation -- in these cases
abort() is ok for you. With my proposal you could use STL or any other
good generic library (instead of developing your own customised
library) and still squeeze maximum performance you could get from it by
'disabling' exceptions you do not want to handle. Also automatic
replacement of 'throw X' with 'abort' will actually preserve your
application state from corruption!
Another case (which happenes to me far too often): you are working on
old huge crappy code base, you know that it is crappy and can't handle
std::bad_alloc for example. I would gladly 'switch off' std::bad_alloc
(gaining performance) and 'abort' instead of 'throw bad_alloc' will
help me to preserve application state from corruption.


> Note that the optimizations you speak of have as an upper limit the
> efficiency of code that doesn't use exception specifications at all, so
> if you're really interested in efficiency, don't use exception
> specifications.

No, not exactly. Since I propose not to use 'bad_exception' -- upper
limit is the efficiency of code that has no-throw for every function
that does not throw in given context. And I propose to refine this even
more: by removing irrelevant exceptions from consideration (i.e. by
replacing 'throw X' with 'abort()' for every exception we do not
want/prepared to handle).

Not using throw-spec is less useful than using no-throw spec. I.e.:

void foo1();
void foo2() throw();

{
    foo1();
    foo2(); // this call would be cheaper since compiler could rely on
no-throw spec
              // (remember -- we are not using bad_exception)
}


Bye.
Sincerely yours, Michael.

P.S. btw instead of 'abort' 'throw X' could be replaced by call to smth
similar to 'unexpected_exception' (which by default calls 'abort' --
yes it is still safe to call abort, not terminate)

---
[ 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: "Michael Pryhodko" <mpryhodko@westpac.com.au>
Date: Sun, 20 Nov 2005 19:19:27 CST
Raw View
> P.S. btw instead of 'abort' 'throw X' could be replaced by call to smth
> similar to 'unexpected_exception' (which by default calls 'abort' --
> yes it is still safe to call abort, not terminate)

Sorry... It should be:
-----
(which by default calls 'exit()' -- yes it is still safe to call
'exit', no need to 'abort', since app state not corrupted yet).
-----

But after another thought -- it is not true, since static data could be
in 'unprepared' state.

Bye.
Sincerely yours, Michael.

---
[ 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: "Michael Pryhodko" <mpryhodko@westpac.com.au>
Date: Sun, 20 Nov 2005 22:20:20 CST
Raw View
Michael Pryhodko wrote:
> I am risking to reinvent wheels again, but recently I have got an idea
> how this could be improved.

My apologies to everyone. I found that my idea is not so good after all
and looks like doomed to be sent to void. I found that C++ compiler
could be implemented so that no-throw function call cost is the same to
others (with very marginal advantage of my idea if call is inlined, but
for the price of having unexpected behavior). Also to after reading C++
standard I have found (to my shame) that I had wrong understanding of
existing throw-spec mechanism.

The only useful benefit of "idea" is to have 'throw X' replaced with
'abort' automatically...
Oh well, on the bright side I've fixed some errors in my head.

Bye.
Sincerely yours, Michael.

---
[ 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: Simon Bone <sf.bone@btinternet.com>
Date: Mon, 21 Nov 2005 09:29:56 CST
Raw View
On Sun, 20 Nov 2005 18:53:24 -0600, Michael Pryhodko wrote:

>> From what I gather, you are proposing to change currently well-defined
>> behavior (although not portable behavior) into undefined behavior. And
>> you want to do so without requiring changes to existing source code- I
>> think that would be a retrograde step.
>
> I disagree. Basically I propose to:
> 1. remove std::bad_exception substitution if throw-spec is violated

But that substitution only happens if you ask for it. From 15.5.2:

"If the exception-specification does not include the class
std::bad_exception then the function terminate() is called" (referring to
what happens when an exception not in the exception-specification is
thrown).

So if you omit std::bad_exception from the throw-spec, you get terminate
called. You want to just pass on the exception and continue unwinding the
stack? I'd rather continue to have terminate() called as defined behavior.
As you point out, most code will not be written to cope with a violated
throw spec.

On the other hand, if you include std::bad_exception in the throw-spec,
you get some implementation defined translation of your disallowed
exception to be passed on. This has little value IMO, but you can avoid it
by not using std::bad_exception in any throw-spec. At least calling code
knows the throw spec, and can try to accommodate it.

Finally, if you really do want *any* exception passed on, just don't
bother with a throw-spec at all. This is the most common case I think.

> 2. introduce mechanism for switching off certain exceptions. I do not
> think that existing source code will suffer as long as it was not
> relying on std::bad_exception. I believe it will benefit.
>

I still don't really get this. If you don't want to throw an exception for
some conditions you just don't write "throw X" for that condition. Why
should the compiler do a special (and weird) transformation on your source
code to avoid doing what you said?

I guess you were most concerned about someone writing "throw Something" in
a library you use. You mention the standard library throwing
std::bad_alloc, but I think that is probably a special case. In general,
don't use a library if you can't live with its exception handling policy.

>
>> However, you are pointing to a real problem. Integrating code that uses
>> exception specs with code that doesn't often results in poor
>> performance because we have no way of saying to the compiler, "Trust
>> me, in this context the functions I call don't throw". That is the
>> statement I'd like to see added to the standard.
>>
>> e.g.
>>
>> int foo();
>>
>> int bar() throw(){
>> _Pragma trust_my_exception_specification return foo();
>> }
>> }
>> This would suppress the compiler generated try/catch equivalent code.
>
> You are reading my mind :-). I was thinking about these hints to
> compiler -- they could help to refine performance further. For example:
>
> vector<int> v;
> v.reserve(v.size() + 1);
> v.push_back(5); // this will not throw for sure
>
> but I was thinking about syntax similar to:
>
> throw_cast<new-spec>(foo()); // at that point compiler 'believes' that
> foo has 'new-spec' exception specification, which is subset of foo's
> original exception specification.
>
>

The syntax could be as you write. I don't have a strong opinion either
way. But this is really about optimization hints, and pragmas have been
used this way before. That's why I chose to write it the way I did.

>> > 4. template functions will get throw-spec on instantiation stage and
>> > will be treated as normal functions afterwards -- in this way good
>> > library (e.g. STL) could be used in most reliable apps and in most
>> > unreliable -- with the same code base you will get safety and speed
>> > (for unreliable apps).
>>
>> I don't understand that. So what if a function currently called in the
>> implementation of a template function throws/doesn't throw? The test
>> should be whether the concept model allows throwing an exception (which
>> I think it usually should). Template specialization can take care of
>> what to do if a significant optimization opportunity exists for some
>> likely set of instantiation arguments including where a no throw
>> guarantee can be of advantage.
>
> Well, I wanted to say:
> - during compilation of given translation unit... - template function
> gets exception specification during template instantiation... - this
> exception specification is generated by compiler based on
> exception-specs of operations used in given template function -- I think
> it is so according to C++ standard - and then to this implicitly
> generated throw-spec compiler applies '-no_exc' options by removing
> 'switched off' exceptions.
>

What I was trying to say was, I don't think the exception spec should be
automatically generated based on the current implementation. That has a
lot of ramifications for code that happens to use the current
implementation-users start to rely on the throw-spec guarantees. What
happens if a small implementation change in a class changes the throw-spec
of say a vector of iterators into a map of pointers to that class...

> For example, if you switch off std::bad_alloc (which could be done in
> 90% of commercial software, and actually it will improve it's
> reliability by killing app before it corrupts itself) -- almost all
> std::vector's members will get no-throw spec after instantiation (lets
> not take element's type into account for now),
>

The elements type is the critical bit though. I don't think it can be
ignored in the language rules!

As far as the standard library exception policy is concerned, I can see a
real value in implementors offering a choice of policies. Many offer
checked vs non-checked iterators now after all. The specific example to
not throw bad_alloc is often requested, so that might be one for
implementors to offer. It would be covered by implementation specific
limits I think, so could be a standard conforming option.

I'm not sure whether changing the standard to allow more policy options is
a good idea. It certainly gets raised pretty frequently though. For
implementors offering different modes to support users needs is a long
standing tradition. But there is a cost in extra testing and complications
for binary compatibility. Standardizing on additional modes is generally
not a good idea. I mostly think this stuff is best left to QoI and market
forces.

Simon Bone

---
[ 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: "Bob Bell" <belvis@pacbell.net>
Date: Mon, 21 Nov 2005 22:51:09 CST
Raw View
Michael Pryhodko wrote:
> What I am proposing is to relax exception specs to the point where it
> does not create run-time overhead, but provide additional performance
> benefits:
> a) by adjusting no-throw spec (no std::bad_exception usage -- compiler
> could use this)
> b) you could 'sacrifice' reliability for speed -- and no corruption of
> application state, please note it. The only 'gate' to corruption is
> that whenever throw-spec is violated:
> (currently): std::bad_exception is thrown
> (proposal): exception is propagated as usual

If I'm reading this correctly, you're suggesting that functions marked
"throw()" should be allowed to actually throw something. This means
that no one can count on such a function actually not throwing. Since
being able to implement exception-safe operations depends on being able
to count on functions that never throw, exception-safety is a serious
problem with your proposal. Any operation that is strongly safe, for
example, could be broken by your suggested change. Example:

class Widget {
   public:
      Widget& operator=(const Widget& arg);

      void swap(Widget& other) throw();
};

Widget& operator=(const Widget& arg)
{
   // can throw, but harmless:
   Widget   copy(arg);

   // modify *this without throwing.
   this->swap(copy);
}

Without your suggested change, the operator=() function is strongly
exception-safe. But if Widget::swap() is allowed to actually throw
something, it can leave the Widget in a corrupted state.

[snip]

> With my proposal you could use STL or any other
> good generic library (instead of developing your own customised
> library) and still squeeze maximum performance you could get from it by
> 'disabling' exceptions you do not want to handle.

Again, if you're interested in performance, avoiding exception
specifications seems to be the best bet; adding exception
specifications tells the compiler to add code that verifies that a
thrown exception is allowed. How can that ever be faster than the same
program minus that extra verification code?

If there are cases where some of that verification code can be
optimized away (say because a no-throw function calls another no-throw
function), it seems that your proposal diminishes the advantages of
those cases, since you're saying that no-throw functions should be
allowed to throw anyway.

> Also automatic
> replacement of 'throw X' with 'abort' will actually preserve your
> application state from corruption!

While at the same time removing all benefits of exceptions in the first
place.

This also seems to open up the possibility of a whole new class of
errors, where different libraries replace different throws with
abort(). Suppose lib A is compiled to replace throw X with abort, but
uses lib B which isn't compiled this way? What happens when a lib A
function calls a lib B function which throws an X?

> Another case (which happenes to me far too often): you are working on
> old huge crappy code base, you know that it is crappy and can't handle
> std::bad_alloc for example. I would gladly 'switch off' std::bad_alloc
> (gaining performance) and 'abort' instead of 'throw bad_alloc' will
> help me to preserve application state from corruption.

Don't know what to tell you there. It sounds like you're substituting
one really bad behavior for another.

Bob

---
[ 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                       ]