January 09, 2020
Am Thu, 09 Jan 2020 12:39:57 -0800 schrieb H. S. Teoh:

> On Thu, Jan 09, 2020 at 07:05:24PM -0000, Johannes Pfau via Digitalmars-d wrote:
>> Am Thu, 09 Jan 2020 08:02:16 -0800 schrieb H. S. Teoh:
> [...]
>> I think everyone who wants to move away from exceptions really wants to move away from two things:
>> 
>> 1) Stack unwinding and the overhead it involves (memory,
>>    implementation complexity, ...)
>> 2) (Forced) dynamic allocation of error state. How often do you
>>    actually use more than 1 word of error state?
> 
> What can you practically throw, though, if you only have 1 word of error state? I suppose you could store a pointer to some exception object in there, but then that brings us back to issue (2). Unless you have some kind of thread-local global for storing exception objects?
> 

As far as I understood Sutter's proposal it's one word of 'context'. But a function actually returns two words (i.e. two words of state), where one is the error category and the second is the context. Essentially std::error_code. In practice the category code is a pointer to a dummy variable in static data. So for example you could do this:

module filesystem;

ubyte FileSystemErrorClass;

enum FileSystemError
{
    doesNotExist,
    PermissionDenied
}

return Error(&FileSystemErrorClass, FileSystemError.doesNotExist);

if (error.class == &FileSystemErrorClass)
{
    auto kind = cast(FileSystemError)error.context;
}

The ErrorClass is really the Tag, the context is then arbitrary (Tag- specific) data. I.e. you could reference TLS data, but you could just as well allocate data manually and store a pointer to heap memory in the context.

Obviously users would not write such low level code, that's just the low level ABI.

-- 
Johannes
January 09, 2020
On Thursday, 9 January 2020 at 21:29:00 UTC, Johannes Pfau wrote:
> As far as I understood Sutter's proposal it's one word of 'context'. But a function actually returns two words (i.e. two words of state), where one is the error category and the second

Well, on x86 a register can be 256 bits or more... But AFAIK C++ implementations do not allocate memory for exceptions dynamically. So you only pay the price of the constructor and potential cache miss.

January 09, 2020
On 09.01.20 11:32, Jonathan M Davis wrote:
> I wouldn't mind us getting rid of Error in favor of killing the program on
> the spot, since that's actually better for debugging,

It really depends. There are situations where it may be better, but if you are trying to debug a non-deterministic condition that happens very rarely on someone else's machine exclusively in release builds, depending on a lot of user input, druntime shutting down the code that is supposed to collect enough information for you to have a chance to figure out what is going on in detail can hardly be described as "better". It should be possible to hook the kill function at least.
January 09, 2020
Am Thu, 09 Jan 2020 21:35:51 +0000 schrieb Ola Fosheim Grøstad:

> On Thursday, 9 January 2020 at 21:29:00 UTC, Johannes Pfau wrote:
>> As far as I understood Sutter's proposal it's one word of 'context'. But a function actually returns two words (i.e. two words of state), where one is the error category and the second
> 
> Well, on x86 a register can be 256 bits or more... But AFAIK C++ implementations do not allocate memory for exceptions dynamically. So you only pay the price of the constructor and potential cache miss.


I don't know the details, but I guess that's the throw by value / catch by reference idiom in C++. Not sure how it's actually implemented.

In addition to constructor + cache miss you'll also have to do RTTI to match the object to the types in the catch handlers (and for the downcast).


-- 
Johannes
January 09, 2020
On Thursday, 9 January 2020 at 22:39:25 UTC, Johannes Pfau wrote:
> I don't know the details, but I guess that's the throw by value / catch by reference idiom in C++. Not sure how it's actually implemented.

It is implementation defined... in clang it uses a dedicated allocator function. But as you can see it does some optimizations:

https://llvm.org/docs/ExceptionHandling.html

> In addition to constructor + cache miss you'll also have to do RTTI to match the object to the types in the catch handlers (and for the downcast).

I assume that is how clang++ does it, but you don't _have_ to use RTTI structures. You could do it other ways.  But in C++ exceptions tend not to be used on the fast-path (meaning, used primarily for unexpected outcomes).

January 09, 2020
On 1/9/2020 2:10 PM, Timon Gehr wrote:
> On 09.01.20 11:32, Jonathan M Davis wrote:
>> I wouldn't mind us getting rid of Error in favor of killing the program on
>> the spot, since that's actually better for debugging,
> 
> It really depends. There are situations where it may be better, but if you are trying to debug a non-deterministic condition that happens very rarely on someone else's machine exclusively in release builds, depending on a lot of user input, druntime shutting down the code that is supposed to collect enough information for you to have a chance to figure out what is going on in detail can hardly be described as "better". It should be possible to hook the kill function at least.

Hooking the kill function is the way to do that - much better than exception unwinding.
January 09, 2020
On 1/9/2020 12:39 PM, H. S. Teoh wrote:
> It's not really the exceptions themselves
> that people object to, but the associated implementation issues.

Yes. They're very costly, even in code that never throws.

An approach I've been using with modest success is to design errors entirely out of the code. For example, in dmd a lot of errors are handled by making a special AST node for "Error". Subsequent code does nothing with Error nodes.

(Analogously to how floating point code deals with errors, it just sets a NaN value, which is sticky.)

Another technique is to check for errors in the data first, then the processing code does not have to check, and cannot fail.

I enjoy trying to set up an API so it cannot fail, then no special code is needed for errors. Of course, this isn't always possible, and isn't a general solution. But it's nice when one can make it work.

P.S. I hate throwing constructors, and would force them to be nothrow in D if I weren't faced with a tsunami of objection to it :-)
January 09, 2020
On 1/5/2020 10:57 AM, Jacob Carlborg wrote:
> 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 ;)

It's a long running joke that whenever bikeshedding emerges around choice of a keyword, someone proposes "static". And if nobody does step up and propose it, I charge into the breach.
January 10, 2020
On 10.01.20 02:28, Walter Bright wrote:
> On 1/9/2020 2:10 PM, Timon Gehr wrote:
>> On 09.01.20 11:32, Jonathan M Davis wrote:
>>> I wouldn't mind us getting rid of Error in favor of killing the program on
>>> the spot, since that's actually better for debugging,
>>
>> It really depends. There are situations where it may be better, but if you are trying to debug a non-deterministic condition that happens very rarely on someone else's machine exclusively in release builds, depending on a lot of user input, druntime shutting down the code that is supposed to collect enough information for you to have a chance to figure out what is going on in detail can hardly be described as "better". It should be possible to hook the kill function at least.
> 
> Hooking the kill function is the way to do that - much better than exception unwinding.

Exception unwinding gives you a stack trace.
January 10, 2020
On Friday, 10 January 2020 at 01:40:00 UTC, Walter Bright wrote:
> On 1/9/2020 12:39 PM, H. S. Teoh wrote:
>> It's not really the exceptions themselves
>> that people object to, but the associated implementation issues.
>
> Yes. They're very costly, even in code that never throws.
>

The longer this discussion drags on, the more I doubt my understanding of this topic.

What exactly is the execution overhead for non-throwing code paths?

I believe I understand the overhead once stack unwinding needs to be performed, but how is code generation affected for the normal path?