Discussion:
[bitc-dev] type-safe modularity challenges for exceptions and error-return
David Jeske
2013-07-29 22:17:03 UTC
Permalink
If we are to achieve a successful combination of type-safety and
run-time-binding modularity, I think we need to look very carefully at
error-condition-mechanisms, as they can be a significant cause of ABI
backward-compatibility challenges when components are updated. Performance
of error-condition mechanisms is also an important issue.

The simple analogy I choose for this problem is to consider how much
traction or trust the standard C library would have ever achieved if it
contained calls to exit(1) after runtime events, if error return codes did
not have a reliable backward-compatible success/fail test, such as the
zero-as-single-success-value pattern, or if certain error conditions
executed sleep(1) or spin-wait before they were returned. This would have
been untenable for programming in the large.

In my view, a successful systems programming environment must (a) rely on
an error-signaling mechanism with a backward-and-forward compatible
success/fail test, must (b) not change the error-generating profile of a
function in successive supposedly-compatible versions (for this is a break
of the ABI), and should (c) not mandate excessive performance overhead for
error-signaling. Further, if we wish to aid the programmer, we should
consider (zz) mechanisms which help assure exhaustive success/fail-handling
and/or exhaustive condition handling when desired.

IMO, most (all?) modular-compilation stack-unroll exception implementations
fail *both* (a) and (b). This includes not only the older C++, but also the
very modern CLR. While catch-all exception handlers might seem like a
backward compatible success/fail test, in the context-of stack-unroll and
non-local exception handling they practically are not. In order to meet
criteria (a), one would need to wrap *every* statement in a catchall try
block, and never handle exceptions non-locally.

Here is a look at some specific mechanisms..

C++: Fails both (a), and (b) and does not provide (zz). Shap recently
quoted 20% as an overhead for it's exception mechanism, which is slower
than return values but nowhere near as bad as some exception unroll costs.

CLR: Fails both (a) and (b) and does not provide (zz). Further, because the
CLR exception unroll mechanism is *very* expensive, library functions often
provide exception and return-value versions. However, if the library author
did not choose to create a return-value version, the library becomes
unusable for high-performance use.

Java: Fails (a) and (b) despite substantial support for (zz), through it's
checked-exception system. It fails (a) and (b) due to frequent user-defined
RuntimeExceptions, which are exacerbated by the cumbersome nature of it's
checked-exception system. -- IMO..there are less-cumbersome ways to
achieve the same level of checked-exception-safety through compiler
inference, requiring throws declaration only at module boundaries (see my
old brainstorm C# checked exception
proposal<http://dj1.willowmail.com/~jeske/unsolicitedDave/csharp_checked_exception_proposal.html>)
If this were fixed, IMO the use of RuntimeExceptions could be minimized or
eliminated. This solution however, is unviable for JVM, as it has no such
assembly/module boundary over which to perform inference.

Google-Go: has no stack-unroll exception mechanism, instead relying on
error-return-values and the backward-compatible zero-as-success-value test.
Google-Go meets both criteria (a) and (b), though it is unfortunate that
so-far it offers no compiler assistance to assure exhaustive
error-condition checking as in (zz). It also carries all the

Objective-C: Contains a C++ like exception mechanism, but standard
MacOS/iOS libraries do not make use of exceptions, instead returning
error-codes. These standard system libraries meet (a) and (b), though if
they used Objective-C exceptions they would not.

I think there are two attractive paths forward for error-condition
signaling:

(1) Structured error return handling. By which I mean a system based on
error-return codes with more try/catch/raise like language syntax, to help
reduce confusion and compiler-assisted exhaustive checking and
declarations, to increase safety. This has the advantage that return-values
are very fast, and the disadvantage of potentially more complex compilers
and module-internal sub-compilation unit invalidation (slower compile
times).

(2) Fully checked-exceptions, with performant setup and unroll, and
module-level inference. By which I mean a system substantially like Java's
checked exception system, but where throws declarations are only required
on module boundaries.

I think it's also worth noting that there was some ML research on
type-polymorphic exceptions implemented using regular-return-values, which
not only solved some interesting exception polymorphism cases but was also
faster than stack-unroll equivalents. It's going to take me some time to
dig up the research reference.
Jonathan S. Shapiro
2013-07-29 22:50:37 UTC
Permalink
David:

Thank you for re-introducing this as a new thread. I appreciate it.


On Mon, Jul 29, 2013 at 3:17 PM, David Jeske <***@gmail.com> wrote:

> In my view, a successful systems programming environment must (a) rely on
> an error-signaling mechanism with a backward-and-forward compatible
> success/fail test, must (b) not change the error-generating profile of a
> function in successive supposedly-compatible versions (for this is a break
> of the ABI), and should (c) not mandate excessive performance overhead for
> error-signaling. Further, if we wish to aid the programmer, we should
> consider (zz) mechanisms which help assure exhaustive success/fail-handling
> and/or exhaustive condition handling when desired.
>

Empirically, this is over-stating the requirements. As a counter-example,
the UNIX ERRNO space has been expanded and updated many times over the
years, and most programs were unaffected by any given change.

What seems to be the case is that systems arrive at a mature set of
"general" error codes that cover all common cases, and then find themselves
adding error codes at the margin to deal with new issues, typically issues
raised by hardware. Often the new codes turn out to be specializations of
some existing code.

Once that maturity is reached, the reality is that most programs just don't
care about the new cases the vast majority of the time, and *intentionally* do
not perform exhaustive error handling.

My observation is that there are really only three patterns for error
handling:

1. The error is handled *very* close to the source, and the recovery
code has intimate knowledge of what errors may arise. In practice, caller
and callee are almost always in the same module and often written by the
same person.
2. An optimistically attempted computation is abandoned in favor of an
alternative. The discipline is that the code which throws the error doesn't
leave observable effects behind. We rarely care *what* error was raised
in this case.
3. The program exits.

A slight extension of [1] is I/O errors, which may propagate a few levels
up before they hit the IO subsystem boundary and get handled. End-of-file
is an interesting example of a place where an exception should *not* be
used, because life works better if that is handled by an out-of-band value.

All I'm trying to say is that I think we can get a practically useful
result without going quite as far as you imply. In the end, there are only
so many ground sources of errors, and these are fairly well enumerable.



> While catch-all exception handlers might seem like a backward compatible
> success/fail test, in the context-of stack-unroll and non-local exception
> handling they practically are not.
>

I just don't agree. This comes down to resource release and finalization
discipline. But pragmatically, a combination of catch-all and automatically
introduced catch-blocks is the best we know how to do in the current state
of the art.

C++: Fails both (a), and (b) and does not provide (zz). Shap recently
> quoted 20% as an overhead for it's exception mechanism, which is slower
> than return values but nowhere near as bad as some exception unroll costs.
>

To be clearer: there's a philosophical question about whether that is
overhead. In the case of the EROS and Coyotos kernels, we know that:


- No exceptions are ever thrown, so there is never a need to catch them.
- The system design is transactional, no observable mutations are made
until we are past the point where all failure-generating checks are
performed, and there are only three things to clean up when a transaction
aborts or succeeds.

So basically, exceptions weren't a good fit, and all of the code produced
to manage them was pure overhead with exactly zero payback.

If you have a system that might actually *throw* an exception - which
includes any system that invokes operator NEW, then that code is reachable
and potentially needed. It has a given cost, but I don't think that cost
can reasonably be viewed as overhead. You can shrink the amount of code
involved if you do a whole-program compile.

I should also note that later compilers do a *much* better job of keeping
exception unwind costs off of the main execution path than they did at the
time. For something like a microkernel, the *placement* of the exception
code remains important (you want to gather all of it into a separate
section) because it's presence in the main code path creates shadows on the
I-cache that reduce utilization very measurably. Gathering the exception
code that way certainly isn't difficult, but I've yet to see a compiler
that does it.



> CLR: Fails both (a) and (b) and does not provide (zz). Further, because
> the CLR exception unroll mechanism is *very* expensive, library functions
> often provide exception and return-value versions. However, if the library
> author did not choose to create a return-value version, the library becomes
> unusable for high-performance use.
>

It's an interesting question why the CLR exception mechanism is so slow. Do
we have any insight into the cause? Is it something as simple as a JIT
issue? Does it have to do with the need to connect to the (completely
busted) windows SEH model? Could it be because Win32 and Win64 callbacks
can cause the exception stack to cross the kernel stack, which is a serious
mess?


> Google-Go: has no stack-unroll exception mechanism, instead relying on
> error-return-values and the backward-compatible zero-as-success-value test.
> Google-Go meets both criteria (a) and (b), though it is unfortunate that
> so-far it offers no compiler assistance to assure exhaustive
> error-condition checking as in (zz). It also carries all the
>

Can you finish that last sentence at your convenience?


General comment: this is something we should come back to when we're
looking at how exception handling works.


shap
David Jeske
2013-07-30 00:32:23 UTC
Permalink
On Jul 29, 2013 3:51 PM, "Jonathan S. Shapiro" <***@eros-os.org> wrote:
> Empirically, this is over-stating the requirements. As a counter-example,
the UNIX ERRNO space has been expanded and updated many times over the
years, and most programs were unaffected by any given change.

This confuses me. Afaik, posix and UNIX errno specifically meets all of
a,b,c. It does not meet zz, which I admit is optional.

My words may have been unclear. My definition of a,b means if I handle only
binary success/fail.. I should be compatible with all future binary updates.

Unix + errno uses a zero return for success and non zero failure. Functions
which don't return errors today won't return errors tommorow. Return values
are fast. This is my criteria a,b,c.

Expection systems which are not 100% declared/checked violate this, since a
function which previously did not throw an exception could begin doing so.
Afaik, no such system exists which also affords runtime binding.
Jonathan S. Shapiro
2013-07-30 01:13:59 UTC
Permalink
On Mon, Jul 29, 2013 at 5:32 PM, David Jeske <***@gmail.com> wrote:

> Unix + errno uses a zero return for success and non zero failure.
>
Yes. Exceptions also do this; either an exception is thrown or it is not.

> Functions which don't return errors today won't return errors tommorow.
>
Historically, this statement is incorrect. Also mispeled [?] The reason it
was possible for this to change is that POSIX uses a side variable, *errno* for
error returns. That meant that every procedure *always* returned an error
value, but caller source code omitted the check when a procedure was not
documented to return errors, and callee source code generally (but not
reliably) left *errno* unmolested if the procedure was not documented to
possibly return errors.

*Because* this was the convention, the addition of necessary new ERRNO
values did, on occasion, break real code. There have even been examples of
client code that *relied* on procedures to leave *errno* alone if they
weren't documented to signal errors. Needless to say, such caller code
tended to be fragile.

> Return values are fast. This is my criteria a,b,c.
>
Yes, though I see no reason why this should *not* be true for exception
handlers when the Liskov implementation technique is used. Indeed, the
Liskov-style implementation should generally be faster, because (a) it is
equally fast in the exceptional return case, but (b) the exceptional return
case is rarely invoked, and (c) the exceptional return case doesn't get in
the way of normal return at the low-level data structure, and (d) being
designed into the language, a proper exception implementation doesn't
require the thread-local storage workarounds that are required for *errno*.

Exception return speed in C++ is complicated by the fact that essentially
every object has a destructor, so the compiler introduces an *appalling* number
of implicit catch blocks. In a managed language, implicit catch blocks only
get introduced around regions that may contain objects with finalizers,
which are comparatively rare.

We also need to be clear that the code associated with the catch blocks and
the catch demux/dispatch isn't counted against the exception mechanism,
because the same code would be necessary to respond to a non-zero error
result. The issues we are trading off are:

When returning an error code:

- There is *always* an error return value that must be dispatched or
propagated.
- Returning both an error result and a procedure result simultaneously
is syntactically awkward.
- *Every* correct procedure return site *must* wrap the return with an
error code check, if only to propagate the possible error. The total number
of source lines of code is approximately doubled by this requirement, and a
great many forward-not-taken (therefore mispredicted) branches are
introduced into the code, with consequent maintenance errors and
performance costs.

When raising an exception:

- Usually no exception is raised. The raised exception case is - forgive
me - exceptional.
- Returning an actual procedure result is syntactically straightforward.
- There are many correct procedure return sites that do *not* require
wrapping, because exception propagation is handled by the out-of-band
unwind code.

In short, *errno* isn't nearly as cheap as you think it is. Assuming a
properly implemented Liskov-style exception mechanism, it is *very* hard to
see how the *errno* mechanism can possibly be more efficient than exception
propagation.

> Expection systems which are not 100% declared/checked violate this, since
> a function which previously did not throw an exception could begin doing
> so.
>
Because *errno* is out of band, this is equally true in the POSIX scheme.

> Afaik, no such system exists which also affords runtime binding.
>
Perhaps not, but I'm unaware of any technical impediment that would
preclude doing so. So far as I know, exceptions will cross dynamic loading
points in C# and class loader boundaries in Java just fine.

The worst that happens in these situations is that you get a privately
declared exception type leaking out into a caller who doesn't know the type
of the particular exception. But the caller still knows that it *is* an
exception. While this is obviously bad hygiene, it's no worse than the
future proofing needed to guard against new future exceptions thrown by
existing callees.

Declared and checked exceptions have been tested in multiple languages. In
every case I know about they were abandoned because experience shows that
the cure is worse than the disease. This is true even when the declaration
is only done at module boundaries.


shap
Jon Zeppieri
2013-07-30 17:52:57 UTC
Permalink
On Mon, Jul 29, 2013 at 9:13 PM, Jonathan S. Shapiro <***@eros-os.org>wrote:
[snip]


> Yes, though I see no reason why this should *not* be true for exception
> handlers when the Liskov implementation technique is used. Indeed, the
> Liskov-style implementation should generally be faster, because (a) it is
> equally fast in the exceptional return case, but (b) the exceptional return
> case is rarely invoked, and (c) the exceptional return case doesn't get in
> the way of normal return at the low-level data structure, and (d) being
> designed into the language, a proper exception implementation doesn't
> require the thread-local storage workarounds that are required for *errno*
> .
>

[snip]

By "the Liskov implementation technique," do you mean CLU-style exceptions
that propagate only to the immediate caller, as in [
http://www-public.it-sudparis.eu/~gibson/Teaching/CSC7322/ReadingMaterial/LiskovSnyder79.pdf
]?
Jonathan S. Shapiro
2013-07-30 19:24:39 UTC
Permalink
On Tue, Jul 30, 2013 at 10:52 AM, Jon Zeppieri <***@bu.edu> wrote:

> By "the Liskov implementation technique," do you mean CLU-style exceptions
> that propagate only to the immediate caller, as in [
> http://www-public.it-sudparis.eu/~gibson/Teaching/CSC7322/ReadingMaterial/LiskovSnyder79.pdf
> ]?
>

Yes, more or less. The key elements of the fast mechanism are:

1. An out-of-band return pointer that is used solely for exceptional return.
2. Moving unwind code out of the main path, such that (a) it doesn't appear
in the primary execution path, and (b) it can be gathered in such a way as
to avoid casting a shadow on the primary path's I-cache resident set.

It's a fairly straightforward extension of the CLU mechanism to bypass call
frames that don't have any catch handlers.


In addition to being a brilliant computer scientist, Barbara is a
fundamentally decent person. Two properties that go together more often
than we tend to acknowledge, but rarely to this degree. Anita Jones and
Bill Wulf are also like that.


shap
Matt Rice
2013-07-30 19:03:31 UTC
Permalink
On Mon, Jul 29, 2013 at 6:13 PM, Jonathan S. Shapiro <***@eros-os.org>wrote:

>
> - *Every* correct procedure return site *must* wrap the return with an
> error code check, if only to propagate the possible error. The total number
> of source lines of code is approximately doubled by this requirement, and a
> great many forward-not-taken (therefore mispredicted) branches are
> introduced into the code, with consequent maintenance errors and
> performance costs.
>
> there are the gcc extensions that enable the likely/unlikely macros which
sees use in this scenerio,
as well as profile directed feedback which may be a more thumb friendly
hammer

http://kernelnewbies.org/FAQ/LikelyUnlikely
David Jeske
2013-07-30 12:23:12 UTC
Permalink
I will accept your view on the performance of Liskov-exceptions vs
propagated return values.

My criteria here is merely that I think slow structured error signaling
mechanisms are a mistake. They are admitted under the notion that
exceptional conditions are rare, but they force us to duplicate our
codebase into two paths, convenient-slow-structured-errors and
fast-unstructured errors.

---

I don't think this look at errno is representative of typical C
return-value error handling is fair. If I call snprintf, or strcpy, or and
number of other stdlib calls, I know what failure modes they have, and
those failure modes are undisturbed over time (whether by mechanism or
convention) If a function has no error output parameter, it has no means to
add error return in the future (other than exit(1), and divide by zero).
This pattern extends into user-authored C today. Functions do not all
return int because of some UNIX errno history.

That situation is much more murky with existing implementations of
exceptions. The routing of runtime exceptions into an unsuspecting call
stack is unpredictable.

Further, there is something which feels incompatible about current stack
unroll propagation and handling generic errors with catch-all try blocks.
Downstream code expects to throw exceptions through intermediate callers,
so how do those intermediate callers handle binary success/fail in the next
immediate level?

In practice, good modules derive their errors from a base-error-class,
making it possible to catch immediate module errors, while allowing
downstream errors (such as network IO errors) to propagate through to
higher level handlers. I do agree this is a good pattern. However, I wish
for greater reliability that a binary success/fail try block will catch all
of a modules errors without inadvertantly catching other errors.

> It's an interesting question why the CLR exception mechanism is so slow.
Do we have any insight into the cause?

I *believe* this is an implementation defined issue related to preservation
of exception source stack frames for reflection and stack back traces. As
such, I *believe* a different implementation could allow runtime disable of
unroll backtrace frame capture for faster unroll performance.

However, I could be wrong. CLR uses stack walking as part of their security
mechanisms, so that might be related. I don't know anything about callback
exceptions crossing the kernel stack.

>> Google-Go: has no stack-unroll exception mechanism, instead relying on
error-return-values and the backward-compatible zero-as-success-value test.
Google-Go meets both criteria (a) and (b), though it is unfortunate that
so-far it offers no compiler assistance to assure exhaustive
error-condition checking as in (zz). It also carries all the
>
> Can you finish that last sentence at your convenience?

... it also carries all of the syntax-overhead of C style error handling,
rather than providing some more convenient syntax akin to try/catch. I
personally feel the #1 benefit of try/catch is assistance in making code
easier to read, by allowing us to simplify or move error handling clauses.
The #2 benefit is compiler knowledge of the assignment validity of other
"out" or "ref" return values. IMO, both of these benefits are orthogonal
to, and can be provided without stack-unroll. I don't understand why Go
didn't include them even if it chooses not to include stack-unroll.
Jonathan S. Shapiro
2013-07-30 19:17:49 UTC
Permalink
On Tue, Jul 30, 2013 at 5:23 AM, David Jeske <***@gmail.com> wrote:

> Further, there is something which feels incompatible about current stack
> unroll propagation and handling generic errors with catch-all try blocks.
> Downstream code expects to throw exceptions through intermediate callers,
> so how do those intermediate callers handle binary success/fail in the next
> immediate level?
>
I'm not sure what you mean by the "next immediate level" here. I do agree
that there are designs in which exceptions are propagated across
intermediate stack frames by design, and that this practice can backfire.
It's one of the places where I think tools would be helpful.

There is a particular idiom which exposes the kind of thing that [I think]
concerns you very clearly. It occurs when a caller passes a first-class
procedure down into a call stack, and the first-class procedure performs a
non-local exit using the exception mechanism by sending a "proprietary"
exception up the call stack. This can work out very badly if the contract
and convention for resource release and state modification in the
intermediate callers isn't *very* carefully specified and *very *carefully
adhered to.


In my personal experience, the problem that tends to get people in trouble
isn't so much the resource release aspect of exception handling as the need
to be careful about mutating retained state in the presence of possible
exceptions. Most people don't really know how to go about writing
transaction-safe programs. What they end up doing instead is making
optimistic mutations and trying to back them out in finally blocks. This
leads to something a bit worse than the code duplication pattern that you
mentioned: it creates what might be termed *reverse* code duplication,
which is harder and more error prone.

And I think it can be argued fairly that this problem is *exacerbated* by
the fact that pass-through error checking in an exception system is
invisible. An unintended, but useful, purpose of the errno checks around
every procedure call is that it forces the programmer to maintain awareness
of all the points where an error might be introduced. It is far too easy in
an exception-based system to perform a mutation and then make a potentially
exceptional procedure call without realizing that you have done so. This is
*especially* true when you have automatic inlining.

One of the mistakes (in my opinion) in C# is that the default checking of
arithmetic can be changed by a compiler switch. People can make strong
arguments for doing it one way or the other by default. The problem in C#
is that the contract is unknown and not part of the interface type, so
programmers must behave as if basic arithmetic can generate exceptions.
Under this assumption in particular, it's almost *impossible* to write
imperative code that unwinds mutations successfully. The burden of recovery
is just too high.

I'm not sure that the error code approach really changes this, except by
making subtle issues more apparent. I *do* think that tooling could go a
long way to bridge the gap.
Bennie Kloosteman
2013-08-01 07:42:20 UTC
Permalink
Yes it was a mistake ...but people just ignore it now .. no one uses that
switch , and if you do things will fail miserably. If you want it then you
need to use a checked or unchecked region.

Ben


On Wed, Jul 31, 2013 at 3:17 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:

>
>
> One of the mistakes (in my opinion) in C# is that the default checking of
> arithmetic can be changed by a compiler switch. People can make strong
> arguments for doing it one way or the other by default. The problem in C#
> is that the contract is unknown and not part of the interface type, so
> programmers must behave as if basic arithmetic can generate exceptions.
> Under this assumption in particular, it's almost *impossible* to write
> imperative code that unwinds mutations successfully. The burden of recovery
> is just too high.
>
>
>
Jonathan S. Shapiro
2013-07-30 19:09:41 UTC
Permalink
On Tue, Jul 30, 2013 at 5:23 AM, David Jeske <***@gmail.com> wrote:

> I will accept your view on the performance of Liskov-exceptions vs
> propagated return values.
>
> My criteria here is merely that I think slow structured error signaling
> mechanisms are a mistake. They are admitted under the notion that
> exceptional conditions are rare, but they force us to duplicate our
> codebase into two paths, convenient-slow-structured-errors and
> fast-unstructured errors.
>

That is an interesting and productive observation. The intent of
*finally* clauses
was to mitigate this, but it isn't clear that they are effective at doing
so.

Do you have any thoughts on how code path duplication of this form might be
avoided?


> I don't think this look at errno is representative of typical C
> return-value error handling is fair. If I call snprintf, or strcpy, or and
> number of other stdlib calls, I know what failure modes they have, and
> those failure modes are undisturbed over time...
>
That's only true because you are looking at selected cases that have
limited environmental dependencies and can therefore be specified with
relative confidence and completeness. Looking at procedures in the IO
portions of the libraries tends to give a different outcome to the
analysis. The error handling mechanism, whatever it may be, has to work for
both scenarios.


> (whether by mechanism or convention) If a function has no error output
> parameter, it has no means to add error return in the future (other than
> exit(1), and divide by zero). This pattern extends into user-authored C
> today.
>
Hmm. So my problem with this is that I *lived* through the history you are
describing. I had occasion to wrestle with it as one of the SVR4
developers. Your description is simply wrong. Any library function is
entitled to modify errno. I agree that a change in status (as to whether
errno is modified) breaks both the API and the ABI. Much worse: it breaks
them *silently*. Nonetheless, it can and has been done.

AT&T had a Change Review Board partly because the impact of this kind of
thing is potentially bad. The job of the CRB was to determine whether the
benefit justified this disruption. The CRB was never more conservative than
when faced with a problem that silently breaks an API/ABI. DEC had a
similar process for VMS. But these situations are real. They happen, and
they can't always be avoided. Ultimately, it's a human judgement call to
decide what to do about them.

And once again: the *reason* that declared exceptions failed in Java is
that the space of exceptions *does* need to expand over time.

That situation is much more murky with existing implementations of
> exceptions. The routing of runtime exceptions into an unsuspecting call
> stack is unpredictable.
>
I'm sure you are saying something useful here, but I can't work out what it
is. It seems to me that the routing of exceptions up the call stack is
quite clear. For example, the triggering of destructors in C++ is quite
clear even when a completely unknown exception traverses the stack. The
unclear part is where a previously unknown (or at least unknown to the
calling developer) may get caught.

I'm pretty sure that I am not understanding what you mean by "routing"
here. Can you explain?



> > It's an interesting question why the CLR exception mechanism is so slow.
> Do we have any insight into the cause?
>
> I *believe* this is an implementation defined issue related to
> preservation of exception source stack frames for reflection and stack back
> traces. As such, I *believe* a different implementation could allow runtime
> disable of unroll backtrace frame capture for faster unroll performance.
>
Oh dear. Yes. That would certainly do it. It's one of the places where
dynamic introspection incurs a *very* high price.

> However, I could be wrong. CLR uses stack walking as part of their
> security mechanisms, so that might be related. I don't know anything about
> callback exceptions crossing the kernel stack.
>
Yes. The stack-based security model of both CLR and JVM is boogered in too
many ways to count. This is certainly one of them.


shap
Bennie Kloosteman
2013-08-01 07:38:50 UTC
Permalink
It is worth noting here that its exceptionally slow in Debug mode ( may be
a factor of 10^5 to 10^6 slower) , in release mode its not too bad... but
its certainly not fast . In debug mode its almsot cerainly building the
stack address via reflection etc and VS debugger may get in there as well.
In release mode you could do 300K- 1M fail / catch exceptions per second
but is that needed ? Is it good programming ? Thats what tryparse type
methods are all about. Its kind of anoying too , sometimes i like to
check thrown exceptions in the debugger and when you have exceptions for
logic you loose that abbility.

Ben


On Tue, Jul 30, 2013 at 8:23 PM, David Jeske <***@gmail.com> wrote:

> to higher level handlers. I do agree this is a good pattern. However, I
> wish for greater reliability that a binary success/fail try block will
> catch all of a modules errors without inadvertantly catching other errors.
>
> > It's an interesting question why the CLR exception mechanism is so slow.
> Do we have any insight into the cause?
>
> I *believe* this is an implementation defined issue related to
> preservation of exception source stack frames for reflection and stack back
> traces. As such, I *believe* a different implementation could allow runtime
> disable of unroll backtrace frame capture for faster unroll performance.
>
> However, I could be wrong. CLR uses stack walking as part of their
> security mechanisms, so that might be related. I don't know anything about
> callback exceptions crossing the kernel stack.
>
>
>
David Jeske
2013-08-01 07:59:02 UTC
Permalink
On Thu, Aug 1, 2013 at 12:38 AM, Bennie Kloosteman <***@gmail.com>wrote:

> It is worth noting here that its exceptionally slow in Debug mode ( may
> be a factor of 10^5 to 10^6 slower) , in release mode its not too bad...
> but its certainly not fast .
>

Agreed, CLR exceptions are *much* slower in debug mode. I *suspect* a
significant portion of this is trapping to the debugger to give it a chance
to do something where the exception was triggered.


> In release mode you could do 300K- 1M fail / catch exceptions per second
> but is that needed ? Is it good programming ? Thats what tryparse type
> methods are all about.
>

Why write code using two different error return methods? Personally I think
writing solid bug-free code once is enough. I'd like to stem the geometric
expansion caused by mechanisms which require us to write two versions of
code.


> Its kind of anoying too , sometimes i like to check thrown exceptions in
> the debugger and when you have exceptions for logic you loose that
> abbility.
>

That is a problem with debuggers and exception handling facilities. We
shouldn't "solve" this by making exceptions slow and then telling
programmers to not use them often, ohh and write a second version of all
your functions incase someone needs them fast.
Bennie Kloosteman
2013-08-01 12:40:59 UTC
Permalink
>
>
>>
> Why write code using two different error return methods? Personally I
> think writing solid bug-free code once is enough. I'd like to stem the
> geometric expansion caused by mechanisms which require us to write two
> versions of code.
>

I dont think this is a big deal
- You only write it once really, the 2nd case is just a wrapper if
Tryparse == fail then throw . A framework could perhaps create such a
wrapper automatically. So it boils down to 2 minutes of code ( maybe 5 if
you want fine grained exceptions) and 100bytes on the exe/dll size per
method.
- Only a handful of methods really need it , methods which occur in
tight loops . In most cases where you see lots of exceptions eg IO the
cost of the operation dwarfs the throw cost of errors .

Hard logic errors are normally handled by finally or left if critical so
its really only parsing and validation . With validation i hit it a bit
, people normally care about defining the type of error and there is no
way of saying in an interface it can throw exception abc ( which is an
issue with exception) so i need to do this

public IEnumerable<ValidationResult> Validate(ValidationContext
validationContext)
{
if (Qty == 0 )
yield return new ValidationResult("No Qty", new string[] {
"Qty" });
}

and wrap it anyway with an exception throwing handler ( though the handler
is in a different class) .

Even tryparse methods can still cause exceptions (ThreadAborted
,OutofMemory , Stackoverun etc ) and the non format exceptions of parse
which is a useful feature. It would be nice if contracts specify what
types of exceptions are normally thrown .. at the moment msdn doco and
reflector searches is the pnly way .

The best use of exceptions is the message pump on a single thread ( or any
kind of work dispatcher) .. where you know with certainty ( excepting :-)
critical failures ) that all errors will be caught regardless and control
returned to the loop. This can be very painful ( require discipline) with
c style calls to unwind go all the way back to the source.


>
>> Its kind of anoying too , sometimes i like to check thrown exceptions
>> in the debugger and when you have exceptions for logic you loose that
>> abbility.
>>
>
> That is a problem with debuggers and exception handling facilities. We
> shouldn't "solve" this by making exceptions slow and then telling
> programmers to not use them often, ohh and write a second version of all
> your functions incase someone needs them fast.
>

I stiill think "exceptions are errors" is a usefull concept even if thrown
and handled .. this is usefull eg for logging ..or debugging as above. eg
what went unusual in the run but was handled vs what went wrong. Note you
can eliminate one type of error but if it was common behavoiour to have a
throw as a fault then you could not do this .
David Jeske
2013-08-01 15:13:44 UTC
Permalink
On Thu, Aug 1, 2013 at 5:40 AM, Bennie Kloosteman <***@gmail.com>wrote:

> - You only write it once really, the 2nd case is just a wrapper if
> Tryparse == fail then throw . A framework could perhaps create such a
> wrapper automatically. So it boils down to 2 minutes of code ( maybe 5 if
> you want fine grained exceptions) and 100bytes on the exe/dll size per
> method.
>
- Only a handful of methods really need it , methods which occur in
> tight loops . In most cases where you see lots of exceptions eg IO the
> cost of the operation dwarfs the throw cost of errors .
>

Your analysis breaks down for me for a few reasons....

(1) When writing library code, you don't know which functions need it,
because you don't know when they will be used in other people's tight
loops. If you don't support fast path error return for everything. you
break the utility of the library for others.

(2) return-value error handling side-steps the compiler's return-value
assignment analysis. (it assumes all out arguments are assigned after the
function returns normally) Personally, I want assignment analysis always,
not only when using slow exception mechanisms. We can't force programmers
to use structured error mechanisms, but we should not force them not to
through performance problems.

(3) I would really like the compiler to help me assure I've handled all
possible fail-classes, and sometimes additionally know that I've
exhaustively handled all currently existing specific failures. This is not
possible if return values are side-stepping structured error mechanisms.
(see below for more commentary here)

----

Note that none of the above require always using a *stack-unroll* exception
mechanism, they simply require that a fast structured error mechanism
exist. However, I think it makes more sense to have one fast structured
error handling mechanism rather than two.

Even tryparse methods can still cause exceptions (ThreadAborted
> ,OutofMemory , Stackoverun etc ) and the non format exceptions of parse
> which is a useful feature.
>

These exceptions are essentially exit(1), thankfully expressed in the more
specific scope in which they are relevant. I have no trouble if these "true
runtime exceptions" have a slow stack unroll.


> It would be nice if contracts specify what types of exceptions are
> normally thrown .. at the moment msdn doco and reflector searches is the
> pnly way .
>

I agree. I also agree with Shap that current checked-exception proposals
have been cures somewhat worse than the disease. Java's checked exception
proposal doesn't even work that well, since I've seen code resort to
throwing runtime exceptions to avoid the cumbersome overhead of checked
exceptions.

Personally, I find promise in experimenting with a few methods... (a)
requiring all exceptions thrown by an assembly to derive from an assembly
global exception-base, (b) inferring/checkin propagation of these 'assembly
exception classes' at module boundaries, (c) condition handlers. --
However, I admit this is an interesting unsolved area.
Jonathan S. Shapiro
2013-08-01 17:18:31 UTC
Permalink
What really happens is that you end up designing the exception code space
twice - once as exceptions and once as error codes. At that point, the
exception is purely a carrier for the error code. You lose exception
inheritance, for example.
Bennie Kloosteman
2013-08-07 14:38:07 UTC
Permalink
I know what your saying but dont know any .NET libs designed like this
... except validation libs..


On Fri, Aug 2, 2013 at 1:18 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:

> What really happens is that you end up designing the exception code space
> twice - once as exceptions and once as error codes. At that point, the
> exception is purely a carrier for the error code. You lose exception
> inheritance, for example.
>
> _______________________________________________
> bitc-dev mailing list
> bitc-***@coyotos.org
> http://www.coyotos.org/mailman/listinfo/bitc-dev
>
>
Bennie Kloosteman
2013-08-07 15:14:01 UTC
Permalink
1) It does work ok on libs eg .NET v1 had only exceptions , people
complained the most about certain functions , v2 added a few methods
where there were perfomance issues namely int.parse . I think this works in
practice because in most cases where you do this its validation and these
libs tend to be under your control .

2) Yes i hate the use of out ... Again for validation though its less of an
issue since validate does not return.

3) Agree.

I dont think the unwinding is the performance issue in C# its the actual
throw you can do the throw in a tight loop with a deep stack and it is
still slow.. eg throw does the following , copy the stack to an exception
object then change all the addresses to string ( which probably does some
reflection) .

There may be an optomized case here.. you are normally only after a single
fast exception and the "fast" excpetion you need are pretty rare . so you
can run a lambda when an exception occurs in a finally block instead of
throw and treat it as uncaught.

eg something like

bool fails = true;
using GlobalHandler (typeof(ArgumentException) , x=> fails = false)
{
// do calls
}

and the compiles checks if a global handler is loaded and runs that instead
of throw ... Still pretty ugly IMHO..

Or better yet just return back to the handler but dont copy the stack and
build the full information to the object if there is a GlobalHandler in
scope..

Another option maybe to do a small creation of an Exception ( eg access/
copy the address to method name map table and stack but dont do anything
) and work it all out when the excepetion is uncaught. You really do not
want to loose the stack with string methods ..for most uncaught exceptions .

Any form of handling is going to be at least 10-100 * slower than an if
condition which returns a value ( due to cache hits and exceptions
preventing optomizations) , so you will need to do that aynway when you
need the highest perfromance , How much code needs that level of
performance on a fail condition ? Anything else is premature opt.. IMHO.
Jonathan S. Shapiro
2013-08-07 20:32:51 UTC
Permalink
On Wed, Aug 7, 2013 at 8:14 AM, Bennie Kloosteman <***@gmail.com>wrote:

> I dont think the unwinding is the performance issue in C# its the
> actual throw you can do the throw in a tight loop with a deep stack and it
> is still slow.. eg throw does the following , copy the stack to an
> exception object then change all the addresses to string ( which probably
> does some reflection) .
>

Right. One reason for this is that there is a commonly used and advocated
C# convention that every throw creates a new object and therefore performs
an allocation (and potential garbage collection). It's not an excessively
bright way to proceed.

Throwing a previously allocated object, by contrast, should be just as fast
as returning a constant. "Just as fast" as in "not differing by more than
one or two total instructions incurred".

Or better yet just return back to the handler but dont copy the stack and
> build the full information to the object if there is a GlobalHandler in
> scope..
>

Why on earth would you ever be copying the stack?


> Any form of handling is going to be at least 10-100 * slower than an if
> condition which returns a value ( due to cache hits and exceptions
> preventing optomizations) ...
>

You're going to have to show me the supporting hard data on this assertion,
because speaking as a compiler guy who is also done processor architecture
this seems like a pretty unlikely assertion.
David Jeske
2013-08-08 00:16:52 UTC
Permalink
On Aug 7, 2013 1:33 PM, "Jonathan S. Shapiro" <***@eros-os.org> wrote:
> Why on earth would you ever be copying the stack?

What scope should the stack backtrace be valid for? Once a finally block
runs (potentially calling a function), the stack is modified so the
original stack has been overwritten.

In practice, I've had even more trouble with the performance of CLR
exceptions while running under a debugger. Even when the exceptions are
caught, they put a massive toll on performance. This seems implementation
defined and fixable.

Backing out of the CLR details..

Bennie, all I want is a fast structured error handling mechanism which
includes assignment analysis. I don't much care is there is another slow
one. I'm happy to ignore it.

The issue I have with slow exception handling is that it generally forces
me into choosing between fast less-safe code with no assignment analysis;
or slow safer code with exceptions. That's a bad deal in my book.
William ML Leslie
2013-08-08 01:35:23 UTC
Permalink
On 8 August 2013 10:16, David Jeske <***@gmail.com> wrote:
>
> On Aug 7, 2013 1:33 PM, "Jonathan S. Shapiro" <***@eros-os.org> wrote:
>> Why on earth would you ever be copying the stack?
>
> What scope should the stack backtrace be valid for? Once a finally block
> runs (potentially calling a function), the stack is modified so the original
> stack has been overwritten.

Both you and Bennie seem to be saying, effectively:

"The handler probably knows if it needs a filled-in stack trace or not"

and you want to be able to communicate that to the raise statement
(which should probably be polymorphic on this condition).

Sounds reasonable to me.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
David Jeske
2013-08-08 02:29:11 UTC
Permalink
On Wed, Aug 7, 2013 at 6:35 PM, William ML Leslie <
***@gmail.com> wrote:

> Both you and Bennie seem to be saying, effectively:
>
> "The handler probably knows if it needs a filled-in stack trace or not"
>
> and you want to be able to communicate that to the raise statement
> (which should probably be polymorphic on this condition).
>

Actually, I was saying that I think the reason we see more expensive throw
is because they are preserving stack-frame-info before it's destroyed. I
think this might be a harder problem than you're suggesting...

The top-level handler definitely wants a stack backtrace, but you don't
know whether you're going to hit the top-level handler or not. If the need
for a stack-backtrace is statically propagated, a handler in the middle
might not want stack info, but it may decide to re-throw the exception, or
re-throw a nested-exception -- in which case you'd like the whole stack
info to reach the top but it's too late to get it.

I need to re-benchmark current runtime throw vs return performance to see
where it's at today. Last time I tested, Java throw was 25 times slower
than return values, and CLR throw was a stunning 1400-times slower than
return values.

I don't know what the right solution is. Perhaps there is a compromise
where throw is a bit more expensive than return, but still really darn
fast.
William ML Leslie
2013-08-08 03:21:39 UTC
Permalink
On 8 August 2013 12:29, David Jeske <***@gmail.com> wrote:
> On Wed, Aug 7, 2013 at 6:35 PM, William ML Leslie
> <***@gmail.com> wrote:
>>
>> Both you and Bennie seem to be saying, effectively:
>>
>> "The handler probably knows if it needs a filled-in stack trace or not"
>>
>> and you want to be able to communicate that to the raise statement
>> (which should probably be polymorphic on this condition).
>
>
> Actually, I was saying that I think the reason we see more expensive throw
> is because they are preserving stack-frame-info before it's destroyed. I
> think this might be a harder problem than you're suggesting...
>
> The top-level handler definitely wants a stack backtrace, but you don't know
> whether you're going to hit the top-level handler or not.

Except that there are some times when you probably could: The target
handler is (dynamically) known and doesn't raise. If the language
doesn't support the 'cause' exception feature, you can allow a raise
as long as the trace in the caught exception is never read and doesn't
escape. Even if the trace does escape by one of these means, there is
a property that you can infer over the handler stack that describes
for which raise statements the stack frame might need to be filled.

Unfortunately, maintaining that kind of stack makes setting up a try
block more expensive than usual (I think it costs nothing on the CLR),
but it's nowhere near as expensive as saguaro stacks or keeping the
entire stack live.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
William ML Leslie
2013-08-08 03:26:19 UTC
Permalink
On 8 August 2013 13:21, William ML Leslie <***@gmail.com> wrote:
> Unfortunately, maintaining that kind of stack makes setting up a try
> block more expensive than usual (I think it costs nothing on the CLR),
> but it's nowhere near as expensive as saguaro stacks or keeping the
> entire stack live.

Actually, I guess that the handler itself could decide if it wants to
set the stack pointer before proceeding or wants to run without
destroying the stack. When an unwind is necessary, we can encode that
into the handler.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
David Jeske
2013-08-08 03:52:14 UTC
Permalink
On Wed, Aug 7, 2013 at 8:26 PM, William ML Leslie <
***@gmail.com> wrote:

> On 8 August 2013 13:21, William ML Leslie <***@gmail.com>
> wrote:
> > Unfortunately, maintaining that kind of stack makes setting up a try
> > block more expensive than usual (I think it costs nothing on the CLR),
> > but it's nowhere near as expensive as saguaro stacks or keeping the
> > entire stack live.
>
> Actually, I guess that the handler itself could decide if it wants to
> set the stack pointer before proceeding or wants to run without
> destroying the stack. When an unwind is necessary, we can encode that
> into the handler.
>

Don't finally clauses interfere with this?
William ML Leslie
2013-08-08 04:11:24 UTC
Permalink
On 8 August 2013 13:52, David Jeske <***@gmail.com> wrote:
> On Wed, Aug 7, 2013 at 8:26 PM, William ML Leslie
> <***@gmail.com> wrote:
>> Actually, I guess that the handler itself could decide if it wants to
>> set the stack pointer before proceeding or wants to run without
>> destroying the stack. When an unwind is necessary, we can encode that
>> into the handler.
>
>
> Don't finally clauses interfere with this?

I could guess at why you ask, but it's probably better if you say it.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
David Jeske
2013-08-08 21:56:05 UTC
Permalink
On Aug 7, 2013 9:11 PM, "William ML Leslie" <***@gmail.com>
wrote:
> > Don't finally clauses interfere with this?
>
> I could guess at why you ask, but it's probably better if you say it.

I interpret your suggestion to mean we install condition handlers which run
on the stack of the error, before stack unroll. They could decide to
capture the stack or not, because it remains above them.

If we wish to preserve that stack for future handlers, no handler can be
allowed to set the stack pointer (unroll) unless it is ending the exception
propagation. This means generated code in handlers needs to reference
captured stack contents without using the stack pointer, because it runs
down in the stack of the error.
Jonathan S. Shapiro
2013-08-08 22:40:22 UTC
Permalink
On Thu, Aug 8, 2013 at 2:56 PM, David Jeske <***@gmail.com> wrote:

> On Aug 7, 2013 9:11 PM, "William ML Leslie" <***@gmail.com>
> wrote:
> > > Don't finally clauses interfere with this?
> >
> > I could guess at why you ask, but it's probably better if you say it.
>
> I interpret your suggestion to mean we install condition handlers which
> run on the stack of the error, before stack unroll. They could decide to
> capture the stack or not, because it remains above them.
>

No no. I'm questioning why you are trying to preserve the stack in the
first place.

> If we wish to preserve that stack for future handlers, no handler can be
> allowed to set the stack pointer (unroll) unless it is ending the exception
> propagation. This means generated code in handlers needs to reference
> captured stack contents without using the stack pointer, because it runs
> down in the stack of the error.
>

Yuck! What you are describing sounds suspiciously like downward exceptions,
which is totally bad and semantically problematic.

At the moment of raise, you have a series of stack frames that look like:

F_main ... F_catch : F_0 ... F_n : F_raise

At the moment of catch, the only remaining frames are:

F_main ... F_catch

That's it. I don't think any of the intermediate frames want any
preservation at all.

I think this whole discussion is confusing a debugging feature for a
production performance problem, and that we are doing so primarily because
CLR confuses them.

Aside: there are much more efficient ways to implement stack capture than
the way CLR does it.



shap
William ML Leslie
2013-08-09 00:19:17 UTC
Permalink
On 09/08/2013 7:57 AM, "David Jeske" <***@gmail.com> wrote:
>
> On Aug 7, 2013 9:11 PM, "William ML Leslie" <***@gmail.com>
wrote:
> > > Don't finally clauses interfere with this?
> >
> > I could guess at why you ask, but it's probably better if you say it.
>
> I interpret your suggestion to mean we install condition handlers which
run on the stack of the error, before stack unroll. They could decide to
capture the stack or not, because it remains above them.
>
> If we wish to preserve that stack for future handlers, no handler can be
allowed to set the stack pointer (unroll) unless it is ending the exception
propagation. This means generated code in handlers needs to reference
captured stack contents without using the stack pointer, because it runs
down in the stack of the error.
>
>

Yes, that is what I was getting at.

I guess you understand that finally is not the problem - the last
instruction branches either to the raise case, continuing to run handlers,
or it handles the exception, setting the stack pointer.

Finally has to expect to run either in the success case, in which case we
are in the original function and should return control there, or we run in
the case of a handled exception, which is as above, or an unhandled
exception which means branching to the next handler. I guess that is a bit
unusual, but that state already needed to be tracked.

You correctly point out that such an implementation requires bundling a
closure or something for finally (or except) allowing it to use variables
elsewhere in the stack. That was already necessary for exception handlers,
because they need to know the value to set the sp if they handle the
exception.

I guess, if in doubt, assume that those before were smarter than you. I
think it sounds like a fun technique, which even allows you to replace an
exception raise with a value, coroutine style. But maybe it is an
experiment for the future, and preserving the traceback should be done in
some other means.
David Jeske
2013-08-09 00:36:12 UTC
Permalink
On Thu, Aug 8, 2013 at 5:19 PM, William ML Leslie <
***@gmail.com> wrote:

> > If we wish to preserve that stack for future handlers, no handler can be
> allowed to set the stack pointer (unroll) unless it is ending the exception
> propagation. This means generated code in handlers needs to reference
> captured stack contents without using the stack pointer, because it runs
> down in the stack of the error.
>
> Yes, that is what I was getting at.
>
> You correctly point out that such an implementation requires bundling a
> closure or something for finally (or except) allowing it to use variables
> elsewhere in the stack. That was already necessary for exception handlers,
> because they need to know the value to set the sp if they handle the
> exception.
>
This "condition handler" method would require some stack-trickiness in the
case of the CLR, as they use stack-walks as a security mechanism. The
security mechanism would need to see the condition handlers running in the
correct security context. This issue doesn't seem insurmountable.

> I guess, if in doubt, assume that those before were smarter than you. I
> think it sounds like a fun technique, which even allows you to replace an
> exception raise with a value, coroutine style. But maybe it is an
> experiment for the future, and preserving the traceback should be done in
> some other means.
>
I'm curious to hear Shap's wisdom about why this condition handler
mechanism is perilous... other than the obvious difficulty maintaining sane
condition handler closure and handler stack capture.
Jonathan S. Shapiro
2013-08-09 01:48:38 UTC
Permalink
On Thu, Aug 8, 2013 at 5:19 PM, William ML Leslie <
***@gmail.com> wrote:

> On 09/08/2013 7:57 AM, "David Jeske" <***@gmail.com> wrote:
> > If we wish to preserve that stack for future handlers, no handler can be
> allowed to set the stack pointer (unroll) unless it is ending the exception
> propagation.
>
That's to strong. First, the backtrace printing mechanism isn't done in a
handler. It's done deep in the runtime. Second, it's perfectly fine for
unwind code to change the stack pointer. All we need to do to support the
backtrace is to save the stack pointer that was current at the time of the
most recent exception on that thread.

If we're running code in a catch block or a finally block, we're no longer
interested in a stack trace from the original raise.

You guys are *way* over-thinking this.
William ML Leslie
2013-08-09 02:35:29 UTC
Permalink
On 9 August 2013 11:48, Jonathan S. Shapiro <***@eros-os.org> wrote:
> On Thu, Aug 8, 2013 at 5:19 PM, William ML Leslie
> <***@gmail.com> wrote:
>>
>> On 09/08/2013 7:57 AM, "David Jeske" <***@gmail.com> wrote:
>> > If we wish to preserve that stack for future handlers, no handler can be
>> > allowed to set the stack pointer (unroll) unless it is ending the exception
>> > propagation.
>
> That's to strong. First, the backtrace printing mechanism isn't done in a
> handler. It's done deep in the runtime. Second, it's perfectly fine for
> unwind code to change the stack pointer. All we need to do to support the
> backtrace is to save the stack pointer that was current at the time of the
> most recent exception on that thread.

That doesn't seem sufficient, because all of the stack above the frame
of the handler can be overwritten by the handler. A pointer into
space that is available for allocation is not useful.

> If we're running code in a catch block or a finally block, we're no longer
> interested in a stack trace from the original raise.

If 'we' was the CLR or Java, then we would be, because we might attach
the original exception (with its traceback) as the 'cause' field.
Some languages allow you to re-raise the original exception, without
replacing its traceback; a useful technique when you don't know
statically which conditions you want to catch, or when you want to log
all exceptions that cross some boundary.

I would like to think that you're right at least when 'we' are bitc -
those of us implementing language runtimes in the language can use our
own mechanisms to describe traceback semantics - except that the
presence of finally blocks shouldn't interfere with the ability of the
runtime to describe the stack that actually raised the exception.
Finally blocks should *not* prevent the thread-exit exception handler
from creating a traceback!

> You guys are way over-thinking this.

Well, naive exceptions - longjmp style - are able to express David's
criteria. If we don't care about being able to create and manipulate
tracebacks for caught exceptions, we're fine. But I agree with the
CLI architects that this is a nice thing to be able to do.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
William ML Leslie
2013-08-09 02:56:47 UTC
Permalink
On 9 August 2013 12:35, William ML Leslie <***@gmail.com> wrote:
> On 9 August 2013 11:48, Jonathan S. Shapiro <***@eros-os.org> wrote:
>> That's to strong. First, the backtrace printing mechanism isn't done in a
>> handler. It's done deep in the runtime. Second, it's perfectly fine for
>> unwind code to change the stack pointer. All we need to do to support the
>> backtrace is to save the stack pointer that was current at the time of the
>> most recent exception on that thread.
>
> That doesn't seem sufficient, because all of the stack above the frame
> of the handler can be overwritten by the handler. A pointer into
> space that is available for allocation is not useful.

I guess that if you really want the traceback, we could make you
declare your intent in the handler.

try:
party_time()
# Extended except form declares that it wants a traceback,
# which will be generated if not provided.
# not saying anything about who can create values of type
# TraceBack.
except (e : OutOfBeer, tb : TraceBack):
logger.exception("Party ran dry", tb)
# extended raise form means no traceback needs to be
# generated if the next handler wants one
# not sure if I want the caller to be able to lie, but oh well
raise e, tb

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
Bennie Kloosteman
2013-08-12 08:35:33 UTC
Permalink
re overthinking..

I really dont think davids performance requirements is feasible eg less
than 10* non exception cost for trivial work and to have a proper stack
trace for release. And by proper i mean Method / assembly names in the
trace - thats so helpful for resolving production bugs ( i really dont
want to go back to rememebering hex address offsets of methods ..) . ..

You have a cost where you dont throw the exception and have cheaper
exceptions but i dont think thats acceptable.

The work on .NET on a throw is clear

Create a new Exception object ( there is your 10* cost already + branch
miss , cache misses from the following etc )
copy methods from the stack to Exception object ( or whole stack )
load / copy map of address to method name.* change stack to strings now (
or in global handler)
unwind stack till a catch handler is found

* i think this map is harder with CLR type assemblies then DLLs , they
dont get unloaded but getting a map of release code may not be easy , the
jit generates them sequentially as seen.. .. Even for native what happens
if the dll is unloaded later... your back to a hex stack trace.

Williams solution where you want a special exception ( or my if ) and you
dont copy the stack etc could be helpful for the validation type cases. eg
NoStackExcption as the base class for validation. As it hapens in the
construction of the throw Exception object there should be no issue with
finalize.

Ben




On Fri, Aug 9, 2013 at 10:56 AM, William ML Leslie <
***@gmail.com> wrote:

> On 9 August 2013 12:35, William ML Leslie <***@gmail.com>
> wrote:
> > On 9 August 2013 11:48, Jonathan S. Shapiro <***@eros-os.org> wrote:
> >> That's to strong. First, the backtrace printing mechanism isn't done in
> a
> >> handler. It's done deep in the runtime. Second, it's perfectly fine for
> >> unwind code to change the stack pointer. All we need to do to support
> the
> >> backtrace is to save the stack pointer that was current at the time of
> the
> >> most recent exception on that thread.
> >
> > That doesn't seem sufficient, because all of the stack above the frame
> > of the handler can be overwritten by the handler. A pointer into
> > space that is available for allocation is not useful.
>
> I guess that if you really want the traceback, we could make you
> declare your intent in the handler.
>
> try:
> party_time()
> # Extended except form declares that it wants a traceback,
> # which will be generated if not provided.
> # not saying anything about who can create values of type
> # TraceBack.
> except (e : OutOfBeer, tb : TraceBack):
> logger.exception("Party ran dry", tb)
> # extended raise form means no traceback needs to be
> # generated if the next handler wants one
> # not sure if I want the caller to be able to lie, but oh well
> raise e, tb
>
> --
> William Leslie
>
> Notice:
> Likely much of this email is, by the nature of copyright, covered
> under copyright law. You absolutely may reproduce any part of it in
> accordance with the copyright law of the nation you are reading this
> in. Any attempt to deny you those rights would be illegal without
> prior contractual agreement.
> _______________________________________________
> bitc-dev mailing list
> bitc-***@coyotos.org
> http://www.coyotos.org/mailman/listinfo/bitc-dev
>
David Jeske
2013-08-13 05:27:36 UTC
Permalink
On Mon, Aug 12, 2013 at 1:35 AM, Bennie Kloosteman <***@gmail.com>wrote:

> The work on .NET on a throw is clear
>
> a) Create a new Exception object ( there is your 10* cost already + branch
> miss , cache misses from the following etc )
> b) copy methods from the stack to Exception object ( or whole stack )
> c) load / copy map of address to method name.* change stack to strings
> now ( or in global handler)
> d) unwind stack till a catch handler is found
>

Why would you need to do "c" during the throw? It seems like you can easily
do this after.

Also, "b" could be done incrementally as the stack is unwound, preventing a
full stack walk in the case the exception is caught near the throw. (though
certainly at some cost)

> Return values are fast. This is my criteria a,b,c.
>


> Yes, though I see no reason why this should not be true for exception
> handlers when the Liskov implementation technique is used.


@Shap - After some digging, I'm now confused about this comment. Were you
talking about the original CLU exception mechanism?

http://www-public.it-sudparis.eu/~gibson/Teaching/CSC7322/ReadingMaterial/LiskovSnyder79.pdf

As of that paper they only allowed one-level exception signaling, which
sounds to me like a form of structured return value.
Bennie Kloosteman
2013-08-16 02:04:05 UTC
Permalink
On Tue, Aug 13, 2013 at 1:27 PM, David Jeske <***@gmail.com> wrote:

> On Mon, Aug 12, 2013 at 1:35 AM, Bennie Kloosteman <***@gmail.com>wrote:
>
>> The work on .NET on a throw is clear
>>
>> a) Create a new Exception object ( there is your 10* cost already +
>> branch miss , cache misses from the following etc )
>> b) copy methods from the stack to Exception object ( or whole stack )
>> c) load / copy map of address to method name.* change stack to strings
>> now ( or in global handler)
>> d) unwind stack till a catch handler is found
>>
>
> Why would you need to do "c" during the throw? It seems like you can
> easily do this after.
>

Exactly the point .. Depends a lot on the architecture... If your mapping
changes eg by a dll or assembly unloading by the time you hit a global
handler you cant do the lookup , also in .NET at least the debugger will
break at the line when your code throws an exception and it has a full
stack... The runtime which does this needs to get invoked to do that..
Defering it till the handler is obvious and i cant imagine the people who
implemented it are so stupid to overlook it , doing this would have been a
lot easier than adding tryparse in v2 of .NET . Maybe this cost is trivial
compared to the stack copy .


> Also, "b" could be done incrementally as the stack is unwound, preventing
> a full stack walk in the case the exception is caught near the throw.
> (though certainly at some cost)
>

Speaking of unwinding you can make this much faster if yoru prepared to
ware a small cost ..when exceptions are not thrown.

> Return values are fast. This is my criteria a,b,c.
>>
>
>
>> Yes, though I see no reason why this should not be true for exception
>> handlers when the Liskov implementation technique is used.
>
>
I can not see anyway that an exception mechanism that even if it produces
no stack as an option and that creates an object to communicate what type
of exception can be faster than say 10* slower for simple validation (
though for IO exceptions its obviously neg. ) . You may be able to get to
2-4* speed penalty for failed exception compared to return if all the
following are true

- An object is not created eg FastException : NoStackException is a static
and when you use these exceptions for throw , the runtime just returns the
static instance ( no inheritance , this can only work for a few hard
coded types )
- You do not store a stack copy
- You can unroll back to the handler fast . eg no deep unwinds

Even more what you can do is heavily dependent on the runtime.
Though it may be possible depending ont practical factors , Im still not
convinced its worth it compared to a few cases of tryparse() - better
returns in other areas eg NAT numbers , GC elimination / regions , native
SIMD support and structures that woirk writh it eg scope for preserving YMM
registers etc etc .


Ben
David Jeske
2013-08-16 03:28:27 UTC
Permalink
On Aug 15, 2013 7:04 PM, "Bennie Kloosteman" <***@gmail.com> wrote:
> Im still not convinced its worth it compared to a few cases of tryparse()

I don't think I've been clear enough. Tryparse is not the problem. The
problem is you can't add try* versions to someone else's library!

I don't see the point of slow exceptions thrown from libraries, since they
have NO IDEA how they will be used, or what failure frequency will be.

I reject the idea that a library author can judge frequency of any failures
other TT Han those during one-time init - and even then it is dicy.
Jonathan S. Shapiro
2013-08-20 19:14:44 UTC
Permalink
Sorry for the delay in responding to this thread - I've been traveling for
the last week.

This whole discussion has me chuckling. Let's see if I can recap the
essence of the discussion:

- We like error codes because they are fast.
- We hate exceptions because, having imposed all manner of extraneous
feature creep on the exception mechanism, it's slower than molasses.

Folks, the root problem with exception performance is not in any way
inherent in exceptions. The root problems are (1) CLR did them badly, and
(2) people keep insisting on features that impose a huge penalty.

First, there is absolutely no need to create a new exception object
whenever you throw an exception. The exception object can be constructed
statically and a reference to it can be thrown. Returning that reference
has identical cost to returning an error code. No, you won't get dynamic
per-exception information, but you were perfectly willing to give that up
by returning error codes, so why is everybody whining about that?

Second, there is absolutely no need to copy stack frames when an exception
is thrown. It is not a requirement that the stack remain immutable, and it
is not a requirement that the stack be preserved from the originally thrown
exception. You are only going to perform a backtrace for an exception that
is *not caught. *In that case, no code has run that might modify the stack,
and the original stack is alive and well. You can then perform a backtrace
from there. All that is needed is to preserve the SP and PC from which the
original throw occurred. Some of you have been imagining richer mechanisms
than this, but once again you all seem perfectly willing to tolerate not
getting those mechanisms in the error code style of design.

Unwinding the stack until a catch handler is found is generally *cheaper* than
conventional error return. Both mechanism are doing precisely the same
thing; the error return case does it more explicitly, but that doesn't
change the cost of it.

Bennie notes that your map may change by DLL unload, but that's wrong. In
order for that to happen, you had to hit a catch handler. At that point the
original exception has been caught and you aren't ever going to see a stack
trace from it. If a subsequent throw occurs, then it obviously can't be
entangled with the DLL unload.


So the real problem here isn't the inherent cost of exceptions at all. It's
the cost of creeping featurism.
Jonathan S. Shapiro
2013-08-20 19:15:52 UTC
Permalink
Let me add that long-term CLR isn't a target of interest. CLR is now 10
years old and needs to be replaced in any case.
Jonathan S. Shapiro
2013-08-20 20:04:55 UTC
Permalink
On Tue, Aug 20, 2013 at 12:15 PM, Jonathan S. Shapiro <***@eros-os.org>wrote:

> Let me add that long-term CLR isn't a target of interest. CLR is now 10
> years old and needs to be replaced in any case.
>

Hmm. I said that badly.

In the short term, CLR is a target of interest. I'm not convinced that we
should allow it's shortcomings to dictate the language design for BitC.

Though I can think of several ways to speed up the current implementation
of CLR exceptions, there's really no getting around the fact that it's a
slow mechanism in the current implementation. The problem, as several
people have noted, is the stack trace mechanism. There are implementation
tricks that would let a CLR-like runtime defer construction of the stack
trace. Notably: any catch block that (a) performs a procedure call *and* (b)
re-throws or throws a chain of exceptions using the InnerException property
must perform it's procedure calls on a new stack fragment so that the
original stack fragment is not overwritten.

Note that forming a new stack fragment is cheap - a whole lot cheaper than
trying to copy the existing stack. In fact, with care, it can be *appended* on
the *existing* stack:

+-----------------+
| Frame with |
| try block |
+=================+
| (Orphaned) |
~ Saved exception |
| Frames |
+-----------------+ <- SP from first exception throw
| New stack |
| fragment used |
| by calls in the |
| catch block |
+-----------------+

The only part of this that is remotely tricky is the need to implement a
"dummy" frame at the top of the catch block stack so that the SP can be
properly restored if the calls made from the catch block return without
producing further exceptions. If those calls *do* produce further
exceptions, then the orphaned stack fragments can be extended, such that
each newly thrown exception in the chain appends a new chunk of orphaned
frames. The only real problem with this is that an *unchained* exception
will be newer on the stack than the [earlier] exception it replaces, and we
won't be able to reclaim the earlier orphan frames until the later
unchained exception is handled. I don't really see that as a major issue.

Alternatively, the stack can be implemented in a distinguished "stack
fragment heap", and a more conventional spaghetti stack can be used. In
that case the unreachable stack chunks can be copied into the conventional
heap at need by the collector, and collected in the usual way. This is a
known set of stack implementation techniques due to Appel.


But the main point I'm trying to make here is that if the implementation is
done intelligently, the cost of throwing an exception is:

1. Save any arguments to the exception object.
2. Save the PC and SP at which the exception is thrown
3. Branch to the exceptional return address.

Note that if the stack is going to be handled orphan-style as described
above, then the exception object can (and should) be stack allocated and
the exception object*'s payload* should be implemented in an unboxed type
that resides on the orphaned stack. It's actually important here that
exceptions cannot be captured to side variables, because that *would* require
copying the orphaned stack fragment *unless* the spaghetti stack
implementation is used.

Now if all of this is done, here's the cost of returning an exception:

PUSH arguments to exception constructor
CALL exception constructor
CALL exception return address, thereby saving PC and SP

Note that the exception constructor is lexically visible to the compiler,
can be inlined, and once inlined is generally a NO-OP (because all the
constructor does is put the constructor arguments into exception object
slots, and this will all peephole away if the exception constructor is
inlined.

So what we really end up with is

PUSH exception fields to stack-allocated exception object
CALL exception return address, thereby saving PC and SP

If there are only one or two exception arguments (which is typical, the
second being the exception object's type tag), this cost compares very
favorably with the cost of normal (or error code) return:

MOVE return value to return register
ADD constant to SP, erasing callee frame
RETURN to caller

and is significantly faster than the pattern in which the callee must
discriminate an error return.



So yes, folks, it's certainly possible to design a seriously boogered
exception mechanism, and I think both Java and CLR managed to accomplish
that. But it's not *necessary* to do so.


In my mind, the real question at hand isn't whether exceptions are slower
than error codes. Implemented correctly, they aren't. The real question is
whether the admittedly poor performance of CLR should push us to adopt
non-exception conventions for BitC standard library design. I think the
answer to that should be "no". CLR is, in my view, a transient target of
convenience. If we can achieve performance in CLR that is comparable to C#,
I think we've met all the goals we need to meet.


Jonathan
William ML Leslie
2013-08-21 04:00:40 UTC
Permalink
On 21 August 2013 06:04, Jonathan S. Shapiro <***@eros-os.org> wrote:
> The only part of this that is remotely tricky is the need to implement a
> "dummy" frame at the top of the catch block stack so that the SP can be
> properly restored if the calls made from the catch block return without
> producing further exceptions.

Well, there's a fair bit that's tricky, and control flow is only one
aspect - but actually I think it's one of the easier aspects.

In the compiler, you know you're compiling an except or finally block.
So what is special about this code - what does it have to look like?

There are three possible forms of control flow out of a handler: we
exit with the current exception, we exit with a new exception, and we
exit having handled the exception. These cover both finally and
blocks that conditionally re-raise the original exception (hopefully,
with the original traceback if it fails).

We can see that 'we exit with a new exception' is just a raise, so
it's a case we are handling with the means we're just describing
anyway, so we are left with two cases. When we have handled the
exception, we know the target, can set the stack pointer and jump
directly to the relevant code - no need to indirect through the raise
trampoline. It's the finally case (or equivalently, reraise) that
will need to be smart about determining the next handler up the chain;
it's here that we expect to have a frame that knows how to find the
next handler and set up its arguments appropriately.

Now, aside from concerns around control flow, in order to run a
handler outside its original stack, you need to be very careful about
how you generate the code. For example, accessing local variables in
the original frame require that you indirect against the original
stack pointer, which means you actually need to obtain it, either by
walking the stack, or by pushing it together with the handler target
onto a block stack.

While I think this is a reasonable way to implement exceptions, you do
need to consider if storing that sort of additional detail and
introducing indirections to otherwise local variables into your
exception handlers is a cost you are willing to pay in bitc for its
default exception mechanism. It's one option, but it's not the
obvious one; other mechanisms such as transitive static or dynamic
inference as to whether or not a stack trace will ever need to be
printed from the current raise may impose lower runtime overhead.

Additionally, depending on how hackers manage "resources" (especially
the type with explicit dynamic extent, like 'with' blocks, linear
types, and stack-allocated RAII objects), the finally case may become
particularly descriptive of how fast we can unwind the stack and the
real 'performance' of the exception mechanism.

--
William Leslie

Notice:
Likely much of this email is, by the nature of copyright, covered
under copyright law. You absolutely may reproduce any part of it in
accordance with the copyright law of the nation you are reading this
in. Any attempt to deny you those rights would be illegal without
prior contractual agreement.
Jonathan S. Shapiro
2013-08-22 16:34:11 UTC
Permalink
On Tue, Aug 20, 2013 at 9:00 PM, William ML Leslie <
***@gmail.com> wrote:

> In the compiler, you know you're compiling an except or finally block.
> So what is special about this code - what does it have to look like?
>
> There are three possible forms of control flow out of a handler: we
> exit with the current exception, we exit with a new exception, and we
> exit having handled the exception. These cover both finally and
> blocks that conditionally re-raise the original exception (hopefully,
> with the original traceback if it fails).
>
Bennie Kloosteman
2013-08-21 13:31:50 UTC
Permalink
Nothing wrong with not having it as your long term target but what is your
long term target ? We may be getting ahead of ourselves but you can still
run a pretty complete Linux box with only C and no other language support (
ie apart from scripting ) these things go like mollasses .

I dont see the JVM or CLR going anywhere , JVM is like 18 years ...nothing
close to these run times and they are expensive build. .Latest trends seems
to be more native compiling. ( via C or via IR) Haskell , Rust , Go ,
Microsoft are going back to native ( winrt and even talk of C# to native) ..

LLVM Jit compiler and GC are going no where fast ... ( nor is Apple
interest in non native stacks for Ios)

Ben




On Wed, Aug 21, 2013 at 3:15 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:

> Let me add that long-term CLR isn't a target of interest. CLR is now 10
> years old and needs to be replaced in any case.
>
> _______________________________________________
> bitc-dev mailing list
> bitc-***@coyotos.org
> http://www.coyotos.org/mailman/listinfo/bitc-dev
>
>
David Jeske
2013-08-21 14:48:55 UTC
Permalink
On Aug 21, 2013 6:32 AM, "Bennie Kloosteman" <***@gmail.com> wrote:
> I dont see the JVM or CLR going anywhere , JVM is like 18 years
...nothing close to these run times and they are expensive build. .Latest
trends seems to be more native compiling. ( via C or via IR) Haskell ,
Rust , Go , Microsoft are going back to native ( winrt and even talk of C#
to native) ..

IMO - It is very important to distinguish runtimes where binary shared
libraries can be compatibly updated- as only these systems can be used for
os-level system libraries.

Once you include this criteria, it becomes clear that C, Objective-C, JVM,
and CLR are virtually the only games in town.

Most of the rest are whole program compilers which must be built on system
libs from one of those other primary environments. (Ocaml, Go, Haskell, D,
C++, etc)
Florian Weimer
2013-08-25 19:41:01 UTC
Permalink
* David Jeske:

> IMO - It is very important to distinguish runtimes where binary shared
> libraries can be compatibly updated- as only these systems can be used for
> os-level system libraries.
>
> Once you include this criteria, it becomes clear that C, Objective-C, JVM,
> and CLR are virtually the only games in town.
>
> Most of the rest are whole program compilers which must be built on system
> libs from one of those other primary environments. (Ocaml, Go, Haskell, D,
> C++, etc)

For the JVM, there are module systems which try to enforce
recompilation of all reverse dependencies. It's a PITA for
large-scale system integrators (particularly if they offer security
support), but these module systems show that many people do not
consider this a deal-braker.
David Jeske
2013-08-25 20:59:14 UTC
Permalink
On Sun, Aug 25, 2013 at 12:41 PM, Florian Weimer <***@deneb.enyo.de> wrote:

> * David Jeske:
> > Most of the rest are whole program compilers which must be built on
> system
> > libs from one of those other primary environments. (Ocaml, Go, Haskell,
> D,
> > C++, etc)
>
> For the JVM, there are module systems which try to enforce
> recompilation of all reverse dependencies. It's a PITA for
> large-scale system integrators (particularly if they offer security
> support), but these module systems show that many people do not
> consider this a deal-braker.
>

Can you provide a reference?

Sounds like embedded systems integrators to me. I admit that the term
"compilation" can be interpreted in a number of ways, especially because a
JIT compiler is effectively doing run-time compilation.

My definition of "whole program compiler" is one which requires source
code, and produces a single unified binary. In order to not be a whole
program compiler, one must be able to injest sub-units precompiled into a
format which has stripped internal symbols, and one must be able to have
multiple programs share binary machine ahead-of-time compiled versions of
some of those subunits (aka shared-binary objects or DLLs).
Florian Weimer
2013-09-25 14:01:02 UTC
Permalink
* David Jeske:

> On Sun, Aug 25, 2013 at 12:41 PM, Florian Weimer <***@deneb.enyo.de> wrote:
>
>> * David Jeske:
>> > Most of the rest are whole program compilers which must be built on
>> system
>> > libs from one of those other primary environments. (Ocaml, Go, Haskell,
>> D,
>> > C++, etc)
>>
>> For the JVM, there are module systems which try to enforce
>> recompilation of all reverse dependencies. It's a PITA for
>> large-scale system integrators (particularly if they offer security
>> support), but these module systems show that many people do not
>> consider this a deal-braker.

> Can you provide a reference?

It's hard to find this explicitly in the documentation, but Maven is
usually used this way. Dependencies are specified using an exact
version (expressing a preference), and by default, the dependencies
are "packaged", which is roughly equivalent to shipping a statically
linked copy (although the JVM still performs its usual late binding,
of course, it's just that the bundled copy is preferred over a
system-wide JAR file which might resied in /usr/share/java).

<http://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-project-dependencies.html#pom-relationships-sect-dependency-scope>
Matt Rice
2013-08-26 08:20:19 UTC
Permalink
On Sun, Aug 25, 2013 at 12:41 PM, Florian Weimer <***@deneb.enyo.de> wrote:
> * David Jeske:
>
>> IMO - It is very important to distinguish runtimes where binary shared
>> libraries can be compatibly updated- as only these systems can be used for
>> os-level system libraries.
>>
>> Once you include this criteria, it becomes clear that C, Objective-C, JVM,
>> and CLR are virtually the only games in town.
>>
>> Most of the rest are whole program compilers which must be built on system
>> libs from one of those other primary environments. (Ocaml, Go, Haskell, D,
>> C++, etc)
>
> For the JVM, there are module systems which try to enforce
> recompilation of all reverse dependencies. It's a PITA for
> large-scale system integrators (particularly if they offer security
> support), but these module systems show that many people do not
> consider this a deal-braker.

I'm not entirely convinced it has to be a PITA for large-scale (or no
more excessively a PITA than large-scale itself is.), at a minimum
this entails recompilation of the module itself and the modules tests,
(basically what we have today in most compilation schemes), but allows
for growth unlike current schemes
Jonathan S. Shapiro
2013-08-26 16:41:54 UTC
Permalink
Wait. *Why* is recompilation of reverse dependencies such a pain? Is it
because each developer has to manage it independently?
Florian Weimer
2013-08-26 20:13:08 UTC
Permalink
* Jonathan S. Shapiro:

> Wait. *Why* is recompilation of reverse dependencies such a pain? Is it
> because each developer has to manage it independently?

That's one aspect, yes.

But there is a class of users who want documentation for *any* change.
I'm not sure how they will react to, say, a TLS library update that
comes with the library update *and* most packages in the distribution
which have been "rebuilt due to a change in dependencies".

There's also an expectation that you only have to do detailed QA on
changed components—which is, of course, an unsafe assumption, just
like updating a DSO written in C without recompiling all reverse
dependencies.
Jonathan S. Shapiro
2013-08-26 20:25:06 UTC
Permalink
On Mon, Aug 26, 2013 at 1:13 PM, Florian Weimer <***@deneb.enyo.de> wrote:

> But there is a class of users who want documentation for *any* change.
> I'm not sure how they will react to, say, a TLS library update that
> comes with the library update *and* most packages in the distribution
> which have been "rebuilt due to a change in dependencies".
>

Why on earth would you do *that*? It wouldn't work, since different changes
would lead to different recompilations, and the supplier of a given update
doesn't have access to all of the dependent libraries.

I'm familiar with the class of user you cite. Their concern has merit in
many situations, but the compiler is inherently part of the TCB of any
system. A recompile, as distinct from a source code change, isn't the kind
of think they should be worried about. I understand that they *will* worry
about it, but I have no sympathy for that concern [1,2] and won't admit it
as a design criteria. The same people have no objection to this recompile
when it is performed by the JIT engine, so too damned bad.

[1] There is a potentially legitimate concern about trojans exploiting
compiler bugs, but that's not what these users are thinking about.
[2] There is a *totally* legitimate concern about validation and testing
for critical apps. Such apps may need to be marked in some fashion as "do
not rebuild". But the countervailing concern is that applications marked in
this way cannot be trusted from a security perspective, because one of
their impacts is to ensure the retention of security holes.


But to be clear, my assumption is that the recompile happens on the system
that is the target of install, and is performed by an AOT compiler that is
a core component of that system. Since the party shipping the change cannot
have any idea what libraries are installed on your machine, there is no way
for them to do all of the necessary recompiles.


> There's also an expectation that you only have to do detailed QA on
> changed components—which is, of course, an unsafe assumption, just
> like updating a DSO written in C without recompiling all reverse
> dependencies.


Yup. And there's an expectation that *I* won't get cancer if I smoke, even
though everybody else does. The two expectations have similar merit.


shap
Florian Weimer
2013-08-26 21:00:02 UTC
Permalink
* Jonathan S. Shapiro:

> On Mon, Aug 26, 2013 at 1:13 PM, Florian Weimer <***@deneb.enyo.de> wrote:
>
>> But there is a class of users who want documentation for *any* change.
>> I'm not sure how they will react to, say, a TLS library update that
>> comes with the library update *and* most packages in the distribution
>> which have been "rebuilt due to a change in dependencies".

> Why on earth would you do *that*?

It's the reality today with GHC and Go (both Google compilers).
It used to be this way with various Common Lisp implementations.

> But to be clear, my assumption is that the recompile happens on the
> system that is the target of install, and is performed by an AOT
> compiler that is a core component of that system.

Yes, that's what common-lisp-controller did, and to a lesser degree,
ASDF. It led to occasional problems, just like class loading errors
as the result of binary compatibility violations in Java. Despite
that, it's probably the way forward for GHC and Go, with some
restrictions. (These language implementations can mix in C code which
is not fully separately compiled, so that without further measures,
you need full C development environment at installation time, not just
the language environment or a smart linker.)

Speaking of linking, there is the ELF prelinking optimization which
performs ahead-of-time linking to avoid run-time relocations, using
system-specific load addresses. It causes endless headaches for
cryptographic libraries in FIPS mode because the hash-based tampering
fails. This might be a misapplication of the FIPS requirements
(especially considering the way these requirements are applied to
extremely dynamic environments with run-time code redefiniton etc.),
but it's the current state of the art.
Florian Weimer
2013-09-25 14:03:25 UTC
Permalink
* Matt Rice:

>> For the JVM, there are module systems which try to enforce
>> recompilation of all reverse dependencies. It's a PITA for
>> large-scale system integrators (particularly if they offer security
>> support), but these module systems show that many people do not
>> consider this a deal-braker.
>
> I'm not entirely convinced it has to be a PITA for large-scale (or no
> more excessively a PITA than large-scale itself is.), at a minimum
> this entails recompilation of the module itself and the modules tests,
> (basically what we have today in most compilation schemes), but allows
> for growth unlike current schemes

And if the compiled bits change as the result of the dependency
upgrade, you have to bump the version number (to ship the new bits),
and repeat the process for the reverse dependencies further along the
graph. And so on.

But maybe that's the only way with the growing interest in
reproducible builds.
Jonathan S. Shapiro
2013-08-22 17:23:38 UTC
Permalink
On Wed, Aug 21, 2013 at 6:31 AM, Bennie Kloosteman <***@gmail.com>wrote:

> Nothing wrong with not having it as your long term target but what is
> your long term target ? We may be getting ahead of ourselves but you can
> still run a pretty complete Linux box with only C and no other language
> support ( ie apart from scripting ) these things go like mollasses .
>


> On Wed, Aug 21, 2013 at 3:15 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:
>
>> Let me add that long-term CLR isn't a target of interest. CLR is now 10
>> years old and needs to be replaced in any case.
>>
>

CLR isn't bad, and you are right that these things take a while to build.
That said, the CLR type system is pretty old compared to the current state
of the art, and it needs a refresh.
Bennie Kloosteman
2013-08-21 13:31:44 UTC
Permalink
> First, there is absolutely no need to create a new exception object
> whenever you throw an exception. The exception object can be constructed
> statically and a reference to it can be thrown. Returning that reference
> has identical cost to returning an error code. No, you won't get dynamic
> per-exception information, but you were perfectly willing to give that up
> by returning error codes, so why is everybody whining about that?
>

How can this be if its a user created type ? Possibly with a param ? I
can see it working for some hand picked excpetions like FastException but
not MyException (appErrorCode);

>
> Second, there is absolutely no need to copy stack frames when an exception
> is thrown. It is not a requirement that the stack remain immutable, and it
> is not a requirement that the stack be preserved from the originally thrown
> exception. You are only going to perform a backtrace for an exception that
> is *not caught. *In that case, no code has run that might modify the
> stack, and the original stack is alive and well. You can then perform a
> backtrace from there. All that is needed is to preserve the SP and PC from
> which the original throw occurred. Some of you have been imagining richer
> mechanisms than this, but once again you all seem perfectly willing to
> tolerate not getting those mechanisms in the error code style of design.
>

To me i really want to know what is on the stack where it throws and in
string format ..eg in release runtime loop throwing out of stack . I can
see the line of code and work out how the error can happen without
reporducing all the conditions.


>
> Unwinding the stack until a catch handler is found is generally *cheaper* than
> conventional error return. Both mechanism are doing precisely the same
> thing; the error return case does it more explicitly, but that doesn't
> change the cost of it.
>

There is a coding style here here .. eg a resource is created cheap and
now has to be unwound .. nor do i believe this unwinding is cheaper..im
compraing it to validation where 90% of the time this is an issue . and
failure is this

int Compare (string str1 , string str2)
{
if ( str1.Length !=str2.Length)
return -1;

// do rarer validation
}

Nothing can be anywhere near this speed .. it will be inlined etc..



> Bennie notes that your map may change by DLL unload, but that's wrong. In
> order for that to happen, you had to hit a catch handler. At that point the
> original exception has been caught and you aren't ever going to see a stack
> trace from it. If a subsequent throw occurs, then it obviously can't be
> entangled with the DLL unload.
>

It cant happen in the CLR ( they took the easy way out - no unloading ) but
was thinking something ( but not exactly ) like concurrent GC thread
runssees no usage / code references to DLL throws it out to save memory (
perhaps even the JIT building the code into a code heap which is managed
as a GC / region) . Thread resumes - stack references are wrong.


>
> So the real problem here isn't the inherent cost of exceptions at all.
> It's the cost of creeping featurism.
>

Agree but i want my stack trace as strings at the source in 99% of live
code projects... :-)

Ben
Jonathan S. Shapiro
2013-08-22 17:04:32 UTC
Permalink
On Wed, Aug 21, 2013 at 6:31 AM, Bennie Kloosteman <***@gmail.com>wrote:

> First, there is absolutely no need to create a new exception object
>> whenever you throw an exception. The exception object can be constructed
>> statically and a reference to it can be thrown. Returning that reference
>> has identical cost to returning an error code. No, you won't get dynamic
>> per-exception information, but you were perfectly willing to give that up
>> by returning error codes, so why is everybody whining about that?
>>
>
> How can this be if its a user created type ? Possibly with a param ? I
> can see it working for some hand picked excpetions like FastException but
> not MyException (appErrorCode);
>

There are a couple of ways this could work:

PreallocatedException = new MyException(appErrorCode);
...
fun f() {
...
throw PreallocatedException;
}

The other option is to stack-allocate the exception. Even if you are
throwing a custom exception, the constructor code for YourException is
known and can be inlined. The thing to remember is that exception objects
don't involve that many fields. Typically the PC at which the exception
occurred and the type tag of the exception object is all you need. In CLR
there seems to be a convention that the exception carries a string-flavored
diagnostic message, but if the string is constant that's just a third
pointer.

Ah. But it does occur to me that I made an unintentional assumption. In
most languages, exceptions are pretty much like any other object, except
that they happen to derive from some standard library Exception class.
These objects are allocated in the usual way. They can be copied, passed
around, and so forth.

But there is another possible design, which is to define Exception as an *
unboxed* type. So when you invoke

throw MyException(parameters)

you are actually returning a *reference* to a stack-allocated, boxed
object. This is a bit odd, because the object is stack allocated on a part
of the stack that has gone out of scope, but if we're going to preserve
that chunk of stack for later backtrace display, there's no problem with
that.



>
>> Second, there is absolutely no need to copy stack frames when an
>> exception is thrown. It is not a requirement that the stack remain
>> immutable, and it is not a requirement that the stack be preserved from the
>> originally thrown exception. You are only going to perform a backtrace for
>> an exception that is *not caught. *In that case, no code has run that
>> might modify the stack, and the original stack is alive and well. You can
>> then perform a backtrace from there. All that is needed is to preserve the
>> SP and PC from which the original throw occurred. Some of you have been
>> imagining richer mechanisms than this, but once again you all seem
>> perfectly willing to tolerate not getting those mechanisms in the error
>> code style of design.
>>
>
> To me i really want to know what is on the stack where it throws and in
> string format ..eg in release runtime loop throwing out of stack . I can
> see the line of code and work out how the error can happen without
> reporducing all the conditions.
>

Well, no, you don't. Because if you really wanted that, you wouldn't be
interested in error codes, because those don't give you any of that.

But assuming you *do* want that, you still don't need to *copy* the stack
frame. What you need to do is *preserve* the stack frames. As long as the
stack frame is preserved, you can produce a pretty string later if you find
that you need it.

So we need to ask: what might cause the stack fragment *not* to be
preserved. The answer is "procedure calls". That is: newly created stack
frames. But wait! Those stack frame creations involve mandatory stores for
initialization purposes. All we need to do is make sure they don't happen
on the stack we are currently trying to preserve. To do that, all we really
need to do is make them happen somewhere else. Which could be on a newly
allocated stack fragment (note this is only allocated in the handler), or
it could be a new section of the existing stack *below* the exception
frames (i.e. in a stack position that we would normally think of as "newer
than" those frames).

Several people here have said that they don't want *anything* in the stack
frame to change because of debugging. I would hazard to guess that even CLR
doesn't provide that. If handler code modifies a parameter, my guess is
that the backtrace shows the modified value. Somebody should check what
happens with a test case. If you want that level of debugging, the right
way to do it is to run a debugger and put a breakpoint on __raise().



>
>> Unwinding the stack until a catch handler is found is generally *cheaper* than
>> conventional error return. Both mechanism are doing precisely the same
>> thing; the error return case does it more explicitly, but that doesn't
>> change the cost of it.
>>
>
> There is a coding style here here .. eg a resource is created cheap and
> now has to be unwound .. nor do i believe this unwinding is cheaper..im
> compraing it to validation where 90% of the time this is an issue . and
> failure is this
>
> int Compare (string str1 , string str2)
> {
> if ( str1.Length !=str2.Length)
> return -1;
>
> // do rarer validation
> }
>
> Nothing can be anywhere near this speed .. it will be inlined etc..
>

This is a poor example, because this isn't a case that should be handled
with exceptions in the first place. In more realistic code, argument
validation failures would be handled as fatal errors, because they indicate
a structural problem in the program. And we really shouldn't worry about a
small marginal cost in what amounts to an exit() call.

I think you are saying that there are good examples of procedures that *
should* return result codes of various forms. I agree. And having
exceptions in the language doesn't prevent that in any way.

But from a type system they are much rarer than you might think. The
problem is that the procedure *must* return a value of the stated return
type, even if it also returns an error code. There are many cases where we
need to generate an error result without having anything plausible to
return. In those situations, there are only two options that preserve type
safety:

1. Exceptions, because the normal return path is not live and therefore the
absence of a normally returned value doesn't matter - though we need to be
careful about initializers in this case.

2. Using union values as return types.



shap
Bennie Kloosteman
2013-08-27 06:19:50 UTC
Permalink
>
>
>>>
>> To me i really want to know what is on the stack where it throws and in
>> string format ..eg in release runtime loop throwing out of stack . I can
>> see the line of code and work out how the error can happen without
>> reporducing all the conditions.
>>
>
> Well, no, you don't. Because if you really wanted that, you wouldn't be
> interested in error codes, because those don't give you any of that.
>

Yes I do .. I have had loops in production causing stack overflow which you
can find by what sequence of methods ran in the loop .. this is invaluable
to reproduce the problem.


>
> But assuming you *do* want that, you still don't need to *copy* the stack
> frame. What you need to do is *preserve* the stack frames. As long as the
> stack frame is preserved, you can produce a pretty string later if you find
> that you need it.
>

Which requires the runtime to gurantee that the adress to lookup is valid .


>
> So we need to ask: what might cause the stack fragment *not* to be
> preserved. The answer is "procedure calls". That is: newly created stack
> frames. But wait! Those stack frame creations involve mandatory stores for
> initialization purposes. All we need to do is make sure they don't happen
> on the stack we are currently trying to preserve. To do that, all we really
> need to do is make them happen somewhere else. Which could be on a newly
> allocated stack fragment (note this is only allocated in the handler), or
> it could be a new section of the existing stack *below* the exception
> frames (i.e. in a stack position that we would normally think of as "newer
> than" those frames).
>
> Several people here have said that they don't want *anything* in the
> stack frame to change because of debugging. I would hazard to guess that
> even CLR doesn't provide that. If handler code modifies a parameter, my
> guess is that the backtrace shows the modified value. Somebody should check
> what happens with a test case. If you want that level of debugging, the
> right way to do it is to run a debugger and put a breakpoint on __raise().
>
>
>
>>
>>> Unwinding the stack until a catch handler is found is generally *cheaper
>>> * than conventional error return. Both mechanism are doing precisely
>>> the same thing; the error return case does it more explicitly, but that
>>> doesn't change the cost of it.
>>>
>>
The explicit nature means resources are carefully tracked instead of being
carefully created and possibly reused.. finalisers will call the
destructor in C++ and dispose , Agree this is coding style and you can do
exceptions the same way - its just people dont because they dont expect
such errors to happen frequently .. eg a method with a return code visibly
gives equal weight to returning errors and doing something if you dont
return you get an error , an exception "hides" the error path and makes it
the callers prroblem.


>> There is a coding style here here .. eg a resource is created cheap and
>> now has to be unwound .. nor do i believe this unwinding is cheaper..im
>> compraing it to validation where 90% of the time this is an issue . and
>> failure is this
>>
>> int Compare (string str1 , string str2)
>> {
>> if ( str1.Length !=str2.Length)
>> return -1;
>>
>> // do rarer validation
>> }
>>
>> Nothing can be anywhere near this speed .. it will be inlined etc..
>>
>
> This is a poor example, because this isn't a case that should be handled
> with exceptions in the first place.
>

Its quite common , does this string match a variaety of values each will
call compare..( in a typical naive impl) , is not null or not string.empty
is the same.

And is precisely the code people use to say exceptions are slow ..and about
the only time you need exceptions to be fast ...

When do you need "Errors" to be fast ..It must be in a loop and
1) When your using erros for control flow .. Which is bad
2) When your validating large amounts of possibly bad data . The above
is exactly the case when validating the strings or similar stuff like is
not null / empty.



> In more realistic code, argument validation failures would be handled as
> fatal errors, because they indicate a structural problem in the program.
> And we really shouldn't worry about a small marginal cost in what amounts
> to an exit() call.
>
>
Think GUI or validate html strings.. in a loop .


> I think you are saying that there are good examples of procedures that *
> should* return result codes of various forms. I agree. And having
> exceptions in the language doesn't prevent that in any way.
>

I have been saying that from the start . In 10 years of C# code i have
never found a case where excpetion performance would make a material
difference with the exception of user validation , errors should not be
common so why should you have them in a loop.

>
> But from a type system they are much rarer than you might think. The
> problem is that the procedure *must* return a value of the stated return
> type, even if it also returns an error code. There are many cases where we
> need to generate an error result without having anything plausible to
> return. In those situations, there are only two options that preserve type
> safety:
>
> 1. Exceptions, because the normal return path is not live and therefore
> the absence of a normally returned value doesn't matter - though we need to
> be careful about initializers in this case.
>
> 2. Using union values as return types.
>
>
Agree thats why validation excpetions are a pain , you tend to have lots of
objects and want to do Validate() but user data often has erros . The
other big ones .. you cant have expected excepetion type on an interface
.. and you cant pass excpetions accross processes so for web GUIS its
easier to return ValidationErrors rather than catch them and exract the
errors to be put into the page.

Ben
Jonathan S. Shapiro
2013-08-27 14:41:51 UTC
Permalink
On Mon, Aug 26, 2013 at 11:19 PM, Bennie Kloosteman <***@gmail.com>wrote:

> But assuming you *do* want that, you still don't need to *copy* the stack
>> frame. What you need to do is *preserve* the stack frames. As long as
>> the stack frame is preserved, you can produce a pretty string later if you
>> find that you need it.
>>
>
> Which requires the runtime to gurantee that the adress to lookup is valid .
>

Sure. But if it isn't, you can't present a stack trace in *either* approach.


>
>>>> In more realistic code, argument validation failures would be handled
>> as fatal errors, because they indicate a structural problem in the program.
>> And we really shouldn't worry about a small marginal cost in what amounts
>> to an exit() call.
>>
>>
> Think GUI or validate html strings.. in a loop .
>

Validating html strings is input validation, not argument validation.
Different problems => different idioms.


> ... You cant have expected excepetion type on an interface ..
>


As I've said before: that's been tried, and it was a horrible practical
failure. Be careful what you wish for.


shap
Ben Kloosterman
2013-08-21 02:52:27 UTC
Permalink
I understand the issue but for it to be an issue than the error path must be
common which is IMHO rare .. eg maths libs rarely need it .. yes if you
send a stack of 0 to a div op ( or similar) you can get a lot of div by
zero exceptions but you as the consumer of the math lib are violating that
errors are rare ( div by zero is a real life error) and you can check that
yourself. If ( I ==0 ) before calling the lib as an optimization ...



The only time you throw / catch exception frequently are 1) Using it for
control flow. Which is bad anyway 2) validation .. validation type work
gets a try parse.. You say we don't know how a library will be used but I
think we have seen a lot of code and we can make prediction how rare things
will be. If 1 in 100 projects have to work their way around it that's no
big deal to me.



Also for someone else lib it's likely the exceptions maybe quite deep and
the stack unwind / finalization cost of intermediate objects will dominate .
So to design the whole lib for any user requires significant constraints eg
cheap unwinding ( so exceptions can't be deep , be careful of finalizers)
with no inheritance etc which is quite unattractive to me ... I don't know
any libs which are designed like this. If users have unusual requirements of
the lib that causes errors then the user should correct them not the lib
designer. Try parse is an example of a common case hence the lib was
changed..




I think this is the key point when you are in c style you carefully make
sure the return path is fast because the code that does it is in front of
you ..when you work with exceptions you forget it and focus more on lib
maintenance , easier to use interface etc..




Ben





From: bitc-dev-***@coyotos.org [mailto:bitc-dev-***@coyotos.org] On
Behalf Of David Jeske
Sent: Friday, 16 August 2013 11:28 AM
To: Discussions about the BitC language
Subject: Re: [bitc-dev] type-safe modularity challenges for exceptions and
error-return




On Aug 15, 2013 7:04 PM, "Bennie Kloosteman" <***@gmail.com
<mailto:***@gmail.com> > wrote:
> Im still not convinced its worth it compared to a few cases of tryparse()

I don't think I've been clear enough. Tryparse is not the problem. The
problem is you can't add try* versions to someone else's library!

I don't see the point of slow exceptions thrown from libraries, since they
have NO IDEA how they will be used, or what failure frequency will be.

I reject the idea that a library author can judge frequency of any failures
other TT Han those during one-time init - and even then it is dicy.
Jonathan S. Shapiro
2013-08-20 19:29:50 UTC
Permalink
On Mon, Aug 12, 2013 at 10:27 PM, David Jeske <***@gmail.com> wrote:

> On Mon, Aug 12, 2013 at 1:35 AM, Bennie Kloosteman <***@gmail.com>wrote:
>
>> Yes, though I see no reason why this should not be true for exception
>> handlers when the Liskov implementation technique is used.
>>
>
> @Shap - After some digging, I'm now confused about this comment. Were you
> talking about the original CLU exception mechanism?
>
>
> http://www-public.it-sudparis.eu/~gibson/Teaching/CSC7322/ReadingMaterial/LiskovSnyder79.pdf
>
> As of that paper they only allowed one-level exception signaling, which
> sounds to me like a form of structured return value.
>

So first, you shouldn't take the implementation that is sketched in that
paper too literally. Yes, structured return values *could* be used, and
that's actually a very good way to reason formally about exception handling
if you don't want to get trapped in the view that exceptions are effects,
but no, that isn't the typical implementation.

The problem with using a structured return value is that the compiler must
emit a test-and-dispatch at every call site. This is more work than you
need to do, since there are fewer catch points than there are procedure
calls. There are several better implementations.

First, note that most calling conventions specify a register to be used for
procedure return values. In the normal (successful) return case this will
hold a value matching the return type of the function (I'm ignoring struct
return here). In the exceptional case, no normal return value is actually
returned, and the same register can be used to return the exception value.
So that's how the *value* gets returned, now what about the receiver
discriminating what happened?

The simplest way to do that is to have (in effect) two return PCs. One used
for normal return and the other used for exceptional return. The act of
returning to the exception return PC subsumes the type tag check that would
otherwise be needed at the point of return.

There are several ways to obtain that alternate return PC:

1. Store it on the call stack explicitly at the point of call.
2. Implement a lookup table that maps from normal return PCs to exceptional
return PCs.
3. Define the exceptional return address to exist at a known constant
offset from the normal return address.
4. Store the exceptional return address in a thread-local variable whenever
a catch block is entered. Look it up there when performing an exceptional
return. In this design the catch block setup records the enclosing catch
block PC, thereby implementing a stack of catch blocks.

In practice, options (2) and (4) are the ones that are commonly used,
mainly because [1] would have required modifications to calling conventions
that were well established when languages supporting exceptions were
introduced. A similar problem exists for closure pointers in legacy ABIs.

Note also that if the exception return PC is handled using technique [4],
then the Clu implementation naturally extends to non-local return (i.e. an
exception returning to a call stack that is several frames up). Once you
have a mechanism in place for "one step" return, the propagation of the
exception can be handled by code that is emitted into the "try" phase of
the try/catch block to store the chaining return information.


Jonathan
David Jeske
2013-08-09 02:36:51 UTC
Permalink
On Aug 8, 2013 6:48 PM, "Jonathan S. Shapiro" <***@eros-os.org> wrote:
> If we're running code in a catch block or a finally block, we're no
longer interested in a stack trace from the original raise.
>
> You guys are way over-thinking this.

... Yes, but what about finally !
Jonathan S. Shapiro
2013-08-08 17:46:15 UTC
Permalink
On Wed, Aug 7, 2013 at 7:29 PM, David Jeske <***@gmail.com> wrote:

> Actually, I was saying that I think the reason we see more expensive throw
> is because they are preserving stack-frame-info before it's destroyed. I
> think this might be a harder problem than you're suggesting...
>

I *think* the conversion only occurs when a breakpoint is encountered. If
that break point is in a catch handler somewhere, and you've already
traversed from the point of throw into that catch handler, the stack frames
between the throw and the handler are already gone forever.

The only way to get a stack backtrace at the point of raise is to actually
put a breakpoint on the primitive _raise (I think that's the name) function.


> The top-level handler definitely wants a stack backtrace, but you don't
> know whether you're going to hit the top-level handler or not..
>

Oh! Finally the light dawns on my marble head. Yes. It all comes back to me
now. I'm completely wrong about the stack reconstruction. The need to
report the trace forces a full stack reconstruction at the moment of throw.

But the right fix is not to do that stack trace reporting! It's a bad idea
in any case.



shap
Jonathan S. Shapiro
2013-08-08 17:41:25 UTC
Permalink
On Wed, Aug 7, 2013 at 5:16 PM, David Jeske <***@gmail.com> wrote:

>
> On Aug 7, 2013 1:33 PM, "Jonathan S. Shapiro" <***@eros-os.org> wrote:
> > Why on earth would you ever be copying the stack?
>
> What scope should the stack backtrace be valid for? Once a finally block
> runs (potentially calling a function), the stack is modified so the
> original stack has been overwritten.
>

I feel like I managed to miss a complete sub-discussion, and I'm flailing
in the ocean a bit here. Yes, the finally block runs, potentially
modifying the stack. So what? Why does perfectly normal execution cause us
to want to copy the stack here?

I think I'm missing the problem you are trying to solve that is prompting
you to want to save the stack.

> In practice, I've had even more trouble with the performance of CLR
> exceptions while running under a debugger. Even when the exceptions are
> caught, they put a massive toll on performance. This seems implementation
> defined and fixable.
>

Yes. What I'm about to say is three parts fuzzy memory and seven parts
speculation, but here's what I *think* is going on:

The catch block back-chain is embedded in the normal stack, but should
really be thought of as a side stack. When an exception is raised, it
follows the code in the chained catch handlers. In a certain sense, this
code is "out of band" w.r.t. the main code. Also, there are various ways in
which this code runs in stack frames that violate the CLR type system. For
example you can be in states where values on the stack don't look right.

When you put a debugger into this picture, all of that cheating has to be
made right. The proper state of the stack frame needs to be reconstructed
so that the debugger can look at it.

My dim recollection is that CLR's policy is to re-create a valid stack
state as soon as a debugger stop occurs, rather than wait for the frame to
be examined.


> Bennie, all I want is a fast structured error handling mechanism which
> includes assignment analysis. I don't much care is there is another slow
> one. I'm happy to ignore it.
>
Can you say what you mean by "assignment analysis" here?

> The issue I have with slow exception handling is that it generally forces
> me into choosing between fast less-safe code with no assignment analysis;
> or slow safer code with exceptions. That's a bad deal in my book.
>
Fair enough. But you're asserting very general conclusions about exception
handling performance on the basis of the poor performance of a single,
questionable implementation (CLR).

I think there's another question to ask here. Is the CLR's approach to
exception handling the real culprit here, or is the real problem the fact
that dynamic introspection exists in CLR?


shap
David Jeske
2013-08-08 22:47:29 UTC
Permalink
On Thu, Aug 8, 2013 at 10:41 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:

> I think I'm missing the problem you are trying to solve that is prompting
> you to want to save the stack.
>

I don't want to save the stack. I'm explaining that I *believe* existing
runtimes (JVM, CLR) have slow-throw specifically because they save the
stack, to assure they can provide stack-backtraces.

> Bennie, all I want is a fast structured error handling mechanism which
>> includes assignment analysis. I don't much care is there is another slow
>> one. I'm happy to ignore it.
>>
> Can you say what you mean by "assignment analysis" here?
>

I mean that returnable products of a function should be known by the
compiler not to contain predictable values in the case of an error return
(whether by structured error return, or structured exception handling)...
Currently we only get this behavior when using slow exceptions. Here is an
example..

SomeType a, b;
try {
a = doSomething(out b);
print(a);
} catch {
// invalid to use a or b here, because the compiler knows they were not
assigned..
}

bool failureReturn = doSomething(out a, out b);
// using a return value for error has screwed up the compiler assignment
analysis
// it thinks a and b are valid here even if if we returned true (failure)

> The issue I have with slow exception handling is that it generally
>> forces me into choosing between fast less-safe code with no assignment
>> analysis; or slow safer code with exceptions. That's a bad deal in my book.
>>
> Fair enough. But you're asserting very general conclusions about exception
> handling performance on the basis of the poor performance of a single,
> questionable implementation (CLR).
>

I'm not at all. I'm simply saying my ideal system's runtime would have only
one **fast and structured** error handling mechanism. My position is...
make it fast or take it out. I don't care if it's fast stack unroll or fast
structured return values.

I'm expressing a preference to end slow exception mechanisms, because when
systems have slow exception mechanisms, they become a geometric expansion
factor in APIs. This happens as programmers effectively have to support
both mechanisms.

I think there are a great many situations where runtime-stack-backtraces
are useful. To this end, it might be reasonable to strike a compromise
where we can, at run-time, decide globally between ultra-fast throw with no
stack-preservation, and pretty-fast throw with stack preservation.
(assuming there is no magic way to have ultra-fast throw and
stack-preservation, which would be even better)


> I think there's another question to ask here. Is the CLR's approach to
> exception handling the real culprit here, or is the real problem the fact
> that dynamic introspection exists in CLR?
>

To understand the cause of CLR's incredibly slow exceptions, we need a real
source of truth. Absent some digging, I think it's partially related to
stack-backtrace-preservation (which is only slightly related to
introspection), and partially related to the stack-walk based security
model. I could be completely wrong though.

As for a more broad view of introspection... (a) some amount of type
information for public types is required for robust late-binding... though
even granular data could be hashes or GUIDs if it isn't already. (b) I
would much prefer to have a robust and *language-supported* bind-time or
compile-time introspection and code-generation or macro facility. I really
dislike having to build IDL-parser/code-generators and then further, to
deal with integrating them into cross-platform build systems.
Jonathan S. Shapiro
2013-08-09 01:45:50 UTC
Permalink
On Thu, Aug 8, 2013 at 3:47 PM, David Jeske <***@gmail.com> wrote:

> On Thu, Aug 8, 2013 at 10:41 AM, Jonathan S. Shapiro <***@eros-os.org>wrote:
>
>> I think I'm missing the problem you are trying to solve that is prompting
>> you to want to save the stack.
>>
>
> I don't want to save the stack. I'm explaining that I *believe* existing
> runtimes (JVM, CLR) have slow-throw specifically because they save the
> stack, to assure they can provide stack-backtraces.
>

Ah. OK. I eventually deduced that was how this discussion started, but
thank you for making it clear.


> Bennie, all I want is a fast structured error handling mechanism which
>>> includes assignment analysis. I don't much care is there is another slow
>>> one. I'm happy to ignore it.
>>>
>> Can you say what you mean by "assignment analysis" here?
>>
>
> I mean that returnable products of a function should be known by the
> compiler not to contain predictable values in the case of an error return
> (whether by structured error return, or structured exception handling)...
> Currently we only get this behavior when using slow exceptions. Here is an
> example..
>
> SomeType a, b;
> try {
> a = doSomething(out b);
> print(a);
> } catch {
> // invalid to use a or b here, because the compiler knows they were
> not assigned..
> }
>

OK. I see what you are saying, but the problem is that you're wrong. A
programmer, using proper discipline, can guarantee that the values
*are* predictable,
and it is very important to do this when writing transactional code. We
shouldn't build a feature into the compiler that precludes transactional
code!

A better way to capture this might be to have a way to mark OUT parameters
according to three possibilities:

- Assigned in all cases
- Untouched on exception (transactional style)
- Undefined on exception

Coming from a processor architect's point of view, the last possibility
strikes me as frought with problems, and better removed from the language,
though if we make it the case that "undefined" means "unusable in the
caller", then it could be okay.


> I'm expressing a preference to end slow exception mechanisms...
>

OK. Sorry about my misunderstanding, and I can definitely get behind this
goal. :-)


> I think there are a great many situations where runtime-stack-backtraces
> are useful. To this end, it might be reasonable to strike a compromise
> where we can, at run-time, decide globally between ultra-fast throw with no
> stack-preservation, and pretty-fast throw with stack preservation.
> (assuming there is no magic way to have ultra-fast throw and
> stack-preservation, which would be even better)
>

That's one possibility. Another is to admit that exceptions have two uses:
intended recoverable and intended fatal, and maybe distinguish those cases
at the point of raise. An intended recoverable exception doesn't produce a
stack trace unless it is completely unhandled (in which case note that the
stack is unmodified). An intended fatal exception *does* produce a stack
trace, possibly slowly.

Not sure that's a good idea - just pondering an option here.


>
>
>> I think there's another question to ask here. Is the CLR's approach to
>> exception handling the real culprit here, or is the real problem the fact
>> that dynamic introspection exists in CLR?
>>
>
> To understand the cause of CLR's incredibly slow exceptions, we need a
> real source of truth. Absent some digging, I think it's partially related
> to stack-backtrace-preservation (which is only slightly related to
> introspection), and partially related to the stack-walk based security
> model. I could be completely wrong though.
>

Yeah. I think backtrace is the culprit. Dynamic introspection leads to a
bunch of *other* perf issues, but I don't think it's the root cause in this
one.


shap
Matt Rice
2013-08-29 15:57:58 UTC
Permalink
On Mon, Jul 29, 2013 at 3:17 PM, David Jeske <***@gmail.com> wrote:


> I think it's also worth noting that there was some ML research on
> type-polymorphic exceptions implemented using regular-return-values, which
> not only solved some interesting exception polymorphism cases but was also
> faster than stack-unroll equivalents. It's going to take me some time to dig
> up the research reference.

Don't think that the following was covered in this thread, apologies
if I missed it.
ML's module system brings about a curious case with exceptions,
for an exception to be handled by name, it must not be hidden within
the module, but making the exception public in the module allows
callers to raise the exception,

i'm told that you can sort of achieve this by having a function which
takes the exception as an argument, and returning a bool if it is the,
call this from the exception handler and re-raise on false

handle exn => if isFooException then ... else raise exn

while that does appear to work, it seems by re-raising other
exceptions we potentially obscure their origin.

I sort of found it interesting in that it doesn't seem to jive with
the rest of the ML module system where there is a definite
hidden/opaque/public structure to it.
Matt Rice
2013-08-29 16:31:23 UTC
Permalink
On Thu, Aug 29, 2013 at 8:57 AM, Matt Rice <***@gmail.com> wrote:


> Don't think that the following was covered in this thread, apologies
> if I missed it.
> ML's module system brings about a curious case with exceptions,
> for an exception to be handled by name, it must not be hidden within
> the module, but making the exception public in the module allows
> callers to raise the exception,
>
> i'm told that you can sort of achieve this by having a function which
> takes the exception as an argument, and returning a bool if it is the,
> call this from the exception handler and re-raise on false
>
> handle exn => if isFooException then ... else raise exn
>
> while that does appear to work, it seems by re-raising other
> exceptions we potentially obscure their origin.
>
> I sort of found it interesting in that it doesn't seem to jive with
> the rest of the ML module system where there is a definite
> hidden/opaque/public structure to it.

FWIW i'm not really sure that the ability to suppress raising an
exception has any practical value, because the raising process
includes the source location of the raise thereby allowing one to
differentiate between the same exception raised multiple times, where
error codes being reused, do not (outside of a debugger).
Jonathan S. Shapiro
2013-08-29 16:50:59 UTC
Permalink
On Thu, Aug 29, 2013 at 9:31 AM, Matt Rice <***@gmail.com> wrote:

> On Thu, Aug 29, 2013 at 8:57 AM, Matt Rice <***@gmail.com> wrote:
> > I sort of found it interesting in that it doesn't seem to jive with
> > the rest of the ML module system where there is a definite
> > hidden/opaque/public structure to it.
>
> FWIW i'm not really sure that the ability to suppress raising an
> exception has any practical value, because the raising process
> includes the source location of the raise thereby allowing one to
> differentiate between the same exception raised multiple times, where
> error codes being reused, do not (outside of a debugger).


I actually see the ML solution as perfectly consistent with the rest of the
module system. The type of an object has to be in scope in order for you to
do anything with it.

In a different language design, the way to prevent external fabrications of
those exception objects would be to make the constructors visible only
within the assembly. ML doesn't have that concept.

shap
Matt Rice
2013-08-29 17:15:33 UTC
Permalink
On Thu, Aug 29, 2013 at 9:50 AM, Jonathan S. Shapiro <***@eros-os.org> wrote:
> On Thu, Aug 29, 2013 at 9:31 AM, Matt Rice <***@gmail.com> wrote:
>>
>> On Thu, Aug 29, 2013 at 8:57 AM, Matt Rice <***@gmail.com> wrote:
>> > I sort of found it interesting in that it doesn't seem to jive with
>> > the rest of the ML module system where there is a definite
>> > hidden/opaque/public structure to it.
>>
>> FWIW i'm not really sure that the ability to suppress raising an
>> exception has any practical value, because the raising process
>> includes the source location of the raise thereby allowing one to
>> differentiate between the same exception raised multiple times, where
>> error codes being reused, do not (outside of a debugger).
>
>
> I actually see the ML solution as perfectly consistent with the rest of the
> module system. The type of an object has to be in scope in order for you to
> do anything with it.
>
> In a different language design, the way to prevent external fabrications of
> those exception objects would be to make the constructors visible only
> within the assembly. ML doesn't have that concept.

I Agree, but ML _does_ have that concept, It just doesn't have it for
exceptions AFAICT, you can afaict have an exception whos constructor
requires an object with a hidden constructor, but then you can take
the opaque object out of an exception created by the module, to throw
one from outside the module... anyhow
Loading...