January 06, 2020
There is another exception handling mechanism which has not been seriously suggested up until now, but now may be the time for it.

The basics are the same.
Up in the call tree is some sort of handler registered.
This will be a delegate. It returns an enum.

This enum will typically have members like error, prematureReturn, continue, and unroll.

The way this delegate get used is that it gets matched bottom up (like we do now), in the thrower. The throw statement will match the delegate to the exception, call the delegate and then proceed to do whatever that function tells it to do. It could assert out, return Return.init, continue doing stuff (which may lead to asserting out), or unroll to the handling point (which is what we want to avoid).

As this would be D only, it could be a lot cheaper and may not exhibit the same limitations as we have now.
January 05, 2020
On Sunday, 5 January 2020 at 13:12:47 UTC, rikki cattermole wrote:
> The way this delegate get used is that it gets matched bottom up (like we do now), in the thrower. The throw statement will match the delegate to the exception, call the delegate and then proceed to do whatever that function tells it to do. It could assert out, return Return.init, continue doing stuff (which may lead to asserting out), or unroll to the handling point (which is what we want to avoid).

When exceptions were discussed in the 80s/90s people looked at recovery that kinda works they way you describe. So when a function fails it can stop and then let some other unit fix the problem and then resume where it halted.

I guess you could do this with coroutines in some elegant fashion.

The problem is really writing the code in a recoverable fashion, which most programmers seem to not be able to do, hence the current "transactional" model of unrolling and the retrying from scratch.

With transactional memory and perhaps even a more generic transactional concept that also covers external resources then you could have complete unrolling without programmer intervention (so that no RAII/destructors have to be written). But that is rather advanced... and more like research topic I guess.

January 06, 2020
On 06/01/2020 2:21 AM, Ola Fosheim Grøstad wrote:
> On Sunday, 5 January 2020 at 13:12:47 UTC, rikki cattermole wrote:
>> The way this delegate get used is that it gets matched bottom up (like we do now), in the thrower. The throw statement will match the delegate to the exception, call the delegate and then proceed to do whatever that function tells it to do. It could assert out, return Return.init, continue doing stuff (which may lead to asserting out), or unroll to the handling point (which is what we want to avoid).
> 
> When exceptions were discussed in the 80s/90s people looked at recovery that kinda works they way you describe. So when a function fails it can stop and then let some other unit fix the problem and then resume where it halted.


Yeah there are quite a few variations on this model and I only know the basics.

I don't think we would need the exception handler delegate to "fix" the problem per-say, only determine what the user code wants to do. But perhaps a company like WekaIO has a use case I haven't thought about that would make this a killer feature for them.

> I guess you could do this with coroutines in some elegant fashion.
> 
> The problem is really writing the code in a recoverable fashion, which most programmers seem to not be able to do, hence the current "transactional" model of unrolling and the retrying from scratch.

Yes. IMO the two most likely used out comes would be rethrows or to return prematurely.

> With transactional memory and perhaps even a more generic transactional concept that also covers external resources then you could have complete unrolling without programmer intervention (so that no RAII/destructors have to be written). But that is rather advanced... and more like research topic I guess.


When I described unrolling, I do mean unrolling using the exception handling mechanism.



Just an idea:

try {

} catch(Exception e) {
//	continue throw;
//	continue;
//	break;
}

Any of the statements in the catch block would opt-in to this new mechanism, with the default remaining what we have now.

We could also support some sort of typedef'd tuple or random struct's, that would force into this new mechanism and default to assert(0);
January 05, 2020
On Sunday, 5 January 2020 at 13:32:24 UTC, rikki cattermole wrote:
> When I described unrolling, I do mean unrolling using the exception handling mechanism.

But how will it be faster if it doesn't recover on the spot?

So, if you have an object like the one you propose you could also make that object a resource manager (held in thread local storage). Then you don' t have to unwind. You just free all the resources the object is holding onto, set a new stack pointer and load in the landing pad in the program counter.

But you need a smart compiler, strict type checking and a cleverly designed runtime.

Anyway, C++ deprecated the the "throw" specifier in C++11 and removed it  completely in C++20. Not sure why D users will be more accepting of having to specify "throw".

More patient user base perhaps.

January 06, 2020
On 06/01/2020 2:51 AM, Ola Fosheim Grøstad wrote:
> On Sunday, 5 January 2020 at 13:32:24 UTC, rikki cattermole wrote:
>> When I described unrolling, I do mean unrolling using the exception handling mechanism.
> 
> But how will it be faster if it doesn't recover on the spot?

If you tell it to unwind, it won't.
The idea is to either assert out or return from the erroring function and let the parent figure it out.
January 05, 2020
On Sunday, 5 January 2020 at 13:56:14 UTC, rikki cattermole wrote:
> On 06/01/2020 2:51 AM, Ola Fosheim Grøstad wrote:
>> But how will it be faster if it doesn't recover on the spot?
>
> If you tell it to unwind, it won't.
> The idea is to either assert out or return from the erroring function and let the parent figure it out.

But I think that the concern that people have is that the codegen will generate landingpads for all stackframes that either catch exceptions or call destructors. And that those landingpads make writing the code optimization stages more difficult.

January 06, 2020
On 06/01/2020 3:27 AM, Ola Fosheim Grøstad wrote:
> On Sunday, 5 January 2020 at 13:56:14 UTC, rikki cattermole wrote:
>> On 06/01/2020 2:51 AM, Ola Fosheim Grøstad wrote:
>>> But how will it be faster if it doesn't recover on the spot?
>>
>> If you tell it to unwind, it won't.
>> The idea is to either assert out or return from the erroring function and let the parent figure it out.
> 
> But I think that the concern that people have is that the codegen will generate landingpads for all stackframes that either catch exceptions or call destructors. And that those landingpads make writing the code optimization stages more difficult.

If you use nothrow, the unwinding option would be disabled, so none of that would be a problem.

But you would lose the ability unwind, I don't think we can do anything about that right now.
January 05, 2020
Am Sun, 05 Jan 2020 12:27:11 +0000 schrieb Gregor Mückl:

> Consulting Agner Fox's microarchitecture manual, current Intel CPUs still take a performance hit when encountering code with a high density of conditional branches. They are sensitive to the number of branch instructions per cache line. I've seen GCC pad code with NOPs for that reason. AMD's branch predictor, on the other hand, is described as perceptron that fares better with repeated patterns of branches and isn't tied to the cache. Branch mispredictions still hurt a lot (~20 cycles). All microcontrollers I'm familiar stall their pipeline on every branch if they're pipelined at all.
> 
> Moving error handling overhead back into the common/fast code path is a step backwards in those cases where performance matters. Desktops and servers can absolutely afford to have the data overhead associated with exceptions and they benefit the most from out of band error handling.
> 
> It would be nice to have choice here.


This certainly needs to be considered, however: Some languages such as go (server!) and C++ with std:error decided to go for error codes for error handling. Here the proposal is more efficient and much more safe than completely manual error code handling.

Also exceptions can be up to 1000 times slower for the error path. So you can now start doing statistics how exceptional your error path has to be for exception handling to be more effective. And if the error path is taken very rarely, it is easy for the branch predictor to predict. I have to admit, if you get rid of allocation and all the other exception complexity, the overhead of pure backtracing is probably less. But it's still significantly more than a simple branch.

Regarding microcontrollers, how many microcontroller projects use full backtracing implementations? Every codebase I've ever seen for these systems uses error codes anyway. So Sutter's proposal would not affect the performance of these systems at all.

The point about increased branch instruction density hurting the success path might still apply. But it's highly dependent on the relative amount of functions which actually use error handling. So it seems difficult to assess the impact without benchmarking an implementation.


> I hope that this doesn't require code to have a throw keyword in every other line. Imagine outer functions of some algorithm where the inner functions have a lot of opportunity to fail:
> 
> auto result1 = throw step1();
> auto result2 = throw step2(result1);
> // etc...
> 
> The value of the keyword decreases rapidly with the number of occurrences.

That's certainly true. I think we need some kind of statistics on the distribution of nothrow / throw functions in D code here. This would also be useful information to make a more informed decision about the throw/ nothrow defaults. As long as calling throwing functions is uncommon, making the call explicit should be beneficial.


> There's recently been a talk about error types in C++ and their evolution:
> 
> https://www.youtube.com/watch?v=coBz_CQ1tJ8&
> 
> As with everything in C++, there's a lot of complexity.

Thanks for the link, I'll have a look at this.

> 
>> Caveats:
>> * I have not thought about how exception chaining fits into all this.
> 
> This would require an exception to be allocated that starts the chain. The error return value could then turn into a pointer to that value. That's similar to how the C++ proposals return exceptions in error value returning functions.

This is actually similar to the way exception chaining is implemented in GDC right now. So that's probably not a hard problem.

> 
>> * These exceptions do not naturally propagate through foreign language
>>   interfaces, although I think we don't guarantee this in D
>> right now
>>   either.
> 
> D exceptions and C++ exception implementations are currently quite incompatible, but the documentation still states that that is an eventual goal of D:
> 
> https://dlang.org/spec/cpp_interface.html#exception-handling


I was actually more thinking of a call chain like this: dfun1 => cppfun => dfun2. If dfun2 throws an exception, you can actually catch the exception in dfun1 (even though you do not know if cppfun properly ran its destructors). However, if dfun2 returns an error code union + flag, this error information does not propagate to dfun1.

-- 
Johannes
January 05, 2020
On 2020-01-04 23:01, JN wrote:

> Kind of like static in C has three different uses.

Of like in D, where it has even more uses ;)

-- 
/Jacob Carlborg
January 05, 2020
On Sunday, 5 January 2020 at 15:02:59 UTC, Johannes Pfau wrote:
> This certainly needs to be considered, however: Some languages such as go (server!) and C++ with std:error decided to go for error codes for error handling.

What is "std::error"? If you mean "std::error_code" then it is for wrapping OS error codes like the ones from Unix. (You can throw it if want...)

The error handling regime in Go is not something anyone should copy. The language lacks features that makes code maintainable over time. Lots of pointless error-handling boilerplate noise. Also, they didn't do it because of speed, Go is not that fast. They did it for no scientific reason, just their own (bad) taste, but they later found that they had to add the awful hack that resembles exceptions, but is a lot more ugly and clunky. Although you can hack it to do what you want, in a rather type-unsafe way. So I do that, but Go is the error handling scarecrow of modern languages... You don't want to write big programs in Go.

Go I useful for small web services because it has a decent runtime, stable language design, few compiler bugs, decent web-service libraries and cloud support. However, the language itself is pretty primitive (for no good reason).