Alternative proposal for mapping P0709 Deterministic Exceptions into C

738 views
Skip to first unread message

Niall Douglas

unread,
Jul 22, 2018, 12:58:37 PM7/22/18
to ISO C++ Standard - Future Proposals
The attached proposal is by a WG14 member who felt that he could improve on my _Fails() proposal (formerly the _Either proposal). It is posted here with his permission.

Comments welcome.

Niall
NewCallingConvention.pdf

inkwizyt...@gmail.com

unread,
Jul 22, 2018, 6:11:53 PM7/22/18
to ISO C++ Standard - Future Proposals
For me `_Fails`  work better because of explicit need for `_Try` and `_Catch`.
This is good because it force user to choose what to do and made this very visible.
Another thing is allow of strong typed custom error types.

gmis...@gmail.com

unread,
Jul 22, 2018, 7:01:00 PM7/22/18
to ISO C++ Standard - Future Proposals

"To discriminate between integers and pointers we require that all pointers
must point to data aligned at an even address, and that all error codes should be
odd. We use then, the least significant bit of the error to discriminate between
a pointer to some data containing further information about the error (error is
even), and an integer that directly encodes information about the error (error
is odd) 1"

This to me seems pretty horrible.

Overall, do we really need this 'expected' kind of type to be quite so constrained and magic, to require an even address etc. etc.
In other words, if the type was actually something that was specified as a 'normal' C/C++ struct without any of those gymnastics and data packing or require some carry register etc., would the code be that dramatically worse that we should require such magic or constraints than a regular type without all the magic and alignment requirements?

Niall Douglas

unread,
Jul 23, 2018, 4:11:45 AM7/23/18
to ISO C++ Standard - Future Proposals
Overall, do we really need this 'expected' kind of type to be quite so constrained and magic, to require an even address etc. etc.
In other words, if the type was actually something that was specified as a 'normal' C/C++ struct without any of those gymnastics and data packing or require some carry register etc., would the code be that dramatically worse that we should require such magic or constraints than a regular type without all the magic and alignment requirements?

Not speaking for the above proposal, but in general, it is currently believed that using something like the CPU carry bit ought to be zero overhead on most CPUs, whereas a discriminant stored in the layout would not be zero overhead on more CPUs.

The situation of RISC-V, which has no status register, is a particular unknown. Our current best guess is that either we keep the discriminant in the structure, or we fiddle with the return address e.g. offset it by four. There are no good options on that architecture.

Niall 

Russell Johnston

unread,
Jul 23, 2018, 1:02:04 PM7/23/18
to ISO C++ Standard - Future Proposals
This seems like a step back from earlier proposals, without any rationale given for any of the differences. For example:

Why switch from the expression-based `return _Fail(error);` to the imperative `name_of_the_current_function.error = error; return _Exception;`? The latter still has the magic `_Exception` value, but now also has the potential for name collisions, the potential to `return _Exception` without ever setting the error, and needless verbosity.

Why place further requirements on the error value? It may be nice to have a standard way to indicate error code vs error pointer, but the "even/odd" requirement breaks the ability to use existing error codes *and* existing error pointers! APIs and their clients will have to agree on *which* error codes/pointers are meaningful regardless, so this defeats the purpose of improving interoperability, and would likely doom this feature to be completely unused.

Why the requirement that the `failed` bit be volatile, and readable only immediately after the call? This seems like a misunderstanding of compiler implementation- register allocators already have the necessary machinery to lift these restrictions, which they must use regularly to handle normal register-based calling conventions regardless.

inkwizyt...@gmail.com

unread,
Jul 23, 2018, 1:30:13 PM7/23/18
to ISO C++ Standard - Future Proposals
On RISC-V then cost will be same to `bool foo(void* ret)`. This will be still "zero overhead", simply for other architectures it will be "negative overhead" compared to equivalent manual checks in code.

Another way could be use RVO-like calling convention. Place for return union with discriminant will be pass as one pointer to function, this storage could even be reused by next function if it return same error type.

Russell Johnston

unread,
Jul 23, 2018, 1:34:04 PM7/23/18
to ISO C++ Standard - Future Proposals


On Monday, July 23, 2018 at 10:30:13 AM UTC-7, Marcin Jaczewski wrote:

On RISC-V then cost will be same to `bool foo(void* ret)`. This will be still "zero overhead", simply for other architectures it will be "negative overhead" compared to equivalent manual checks in code.

Another way could be use RVO-like calling convention. Place for return union with discriminant will be pass as one pointer to function, this storage could even be reused by next function if it return same error type.

A third option is to match the behavior of other platforms, and store the discriminant in an extra register. There should already be plenty of caller-saved registers in the ABI, used primarily for passing arguments- their use could be extended to the return value's discriminant. This would also be "negative overhead" compared to passing a pointer.

Niall Douglas

unread,
Jul 23, 2018, 1:54:30 PM7/23/18
to ISO C++ Standard - Future Proposals

This seems like a step back from earlier proposals, without any rationale given for any of the differences. For example:

Just to be clear, this is not my proposal. This is a counterproposal to my proposal by a WG14 member who feels that my proposal does not suit C well.

Niall 

Vinnie Falco

unread,
Jul 23, 2018, 6:07:24 PM7/23/18
to ISO C++ Standard - Future Proposals
On Sunday, July 22, 2018 at 9:58:37 AM UTC-7, Niall Douglas wrote:
Comments welcome.

I like the idea of recycling the function name to set the return value. Is that original in the paper or has the idea been expressed before?

Thanks
 

Bengt Gustafsson

unread,
Jul 23, 2018, 6:19:28 PM7/23/18
to ISO C++ Standard - Future Proposals
Would it be unthinkable for C to implement this limited form of throw/catch system in full? After all almost all C compilers are also C++ compilers so the implementation job is probably negative (you don't have to differentiate between languages in the compiler code).

I guess the main problem would be the keywords as such, but maybe the __throw/__except that is already used (and thereby reserved) in MSVC can be used at least. 

Jake Arkinstall

unread,
Jul 23, 2018, 9:55:11 PM7/23/18
to std-pr...@isocpp.org
Error handling via an effective static state isn't thread safe. Marking the flag as volatile and telling users to check for errors immediately after the function call goes some way to help, I guess, but it's not sufficient to guarantee that errors won't crop up in the wrong thread (or do I have some reading to do?)

This issue is conceded in the paper, albeit referenced as "complicated function calls", but later they state that the C standard library should make this ubiquitous.

It follows that any code using this approach in future (and any code that calls that code, and so on) is not thread safe. Any error which occurs in one thread could pop up as an error in a different thread - and depending how deep that error handling code is in a library, this could have a devastating impact.

An option is to have the error structs as an array indexed by thrd_current(). This means dynamic allocation and the introduction of helper functions rather than the nice direct struct member access, and thus it is unlikely to gain much traction.

Nicol Bolas

unread,
Jul 24, 2018, 12:43:57 AM7/24/18
to ISO C++ Standard - Future Proposals
On Monday, July 23, 2018 at 9:55:11 PM UTC-4, Jake Arkinstall wrote:
Error handling via an effective static state isn't thread safe. Marking the flag as volatile and telling users to check for errors immediately after the function call goes some way to help, I guess, but it's not sufficient to guarantee that errors won't crop up in the wrong thread (or do I have some reading to do?)

I think this is a misunderstanding of what the paper is saying. It is not talking about "static state" (even though it appears to). The `funcName.failed` bit is not intended to be a static property of `funcName`; it's closer to an invisible stack variable, which you access by using the function's name. The compiler turns "funcName.failed" into the specific stack location or register or whatever that maps to that data.

Everything is on the stack, so nothing from other threads can influence this one. That is, two functions in two threads that both call `funcName` and attempt to access `funcName.failed` will be accessing stack data/registers, not static data.

Niall Douglas

unread,
Jul 24, 2018, 4:20:24 AM7/24/18
to ISO C++ Standard - Future Proposals
On Monday, July 23, 2018 at 11:19:28 PM UTC+1, Bengt Gustafsson wrote:
Would it be unthinkable for C to implement this limited form of throw/catch system in full? After all almost all C compilers are also C++ compilers so the implementation job is probably negative (you don't have to differentiate between languages in the compiler code).

The next draft of my D1095 proposal is a one-to-one mapping into C of P0709 deterministic exceptions + P1028 std::error. The mechanism I think has consensus on WG14. The choice of standard error object (std::error) is disliked by a significant minority in C because you must fall back onto either a library or macro based API to construct and inspect such objects.

Now, me personally, I find that acceptable, though obviously laborious to work with. Some on WG14 want the compiler to hide more boilerplate. Hence the counterproposal.

The next draft of D1095 I think won't mention any standard error object. That can be another paper for WG14, and we in C++ can simply provide a C macro API for P1028 like Boost.Outcome does.
 

I guess the main problem would be the keywords as such, but maybe the __throw/__except that is already used (and thereby reserved) in MSVC can be used at least. 


Those keywords will always implement type based C++ exception handling. You must remember that those are not going anywhere in future C++. We'll have two exception throwing mechanisms, value based and type based. This is unavoidable if you wish to preserve backwards binary compatibility. I also suspect that std::uncaught_exceptions() and possibly std::exception_ptr is unimplementable in deterministic exceptions, so code relying on them would break if we simply switched all exceptions to the new mechanism.

(Just to be clear, I think a compiler flag should be available which turns on all-deterministic exceptions, with legacy exceptions emulated, but with some functions like the above no longer compiling in some circumstances. But I don't think it should be default)

Niall

Niall Douglas

unread,
Jul 24, 2018, 4:22:07 AM7/24/18
to ISO C++ Standard - Future Proposals

I like the idea of recycling the function name to set the return value. Is that original in the paper or has the idea been expressed before?

It's the OP's idea.

I personally find it confusing. Function names are pointers to the function's implementation. Accessing it like a struct makes one assume such storage is similarly static.

Niall

Gašper Ažman

unread,
Jul 24, 2018, 4:25:35 AM7/24/18
to std-pr...@isocpp.org
If we are to use the function's name, how do we deal with unutterable functions, like lambdas, or methods? You can dismiss it as "right, C doesn't have those", but they might in the future. I don't think it's a good idea at all.

I also don't like that if I rename the function, I now have to change its body. That makes very little sense to me. A rose by any other name *should* smell as sweet.

Gašper

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/5e1086ff-802e-4e45-bd6c-077a93287b08%40isocpp.org.

David Brown

unread,
Jul 24, 2018, 5:08:45 AM7/24/18
to std-pr...@isocpp.org
On 24/07/18 10:25, Gašper Ažman wrote:
> If we are to use the function's name, how do we deal with unutterable
> functions, like lambdas, or methods? You can dismiss it as "right, C
> doesn't have those", but they might in the future. I don't think it's a
> good idea at all.
>
> I also don't like that if I rename the function, I now have to change
> its body. That makes very little sense to me. A rose by any other name
> *should* smell as sweet.
>

Having programmed in Pascal, where you use the function's name as the
return value pseudo-variable, I agree with that - it is a pain.

It may also be a challenge for recursive functions, and it will
certainly be a problem if you want to use the function more than once
before checking for errors:

x = foo(y) + foo(z);
if (foo.error) ... // Which foo call?

I appreciate the desire for cheap and standardised error handling, but
this syntax is not going to work.



David Brown

unread,
Jul 24, 2018, 5:16:25 AM7/24/18
to std-pr...@isocpp.org
On 23/07/18 10:11, Niall Douglas wrote:
> Overall, do we really need this 'expected' kind of type to be quite
> so constrained and magic, to require an even address etc. etc.
> In other words, if the type was actually something that was
> specified as a 'normal' C/C++ struct without any of those gymnastics
> and data packing or require some carry register etc., would the code
> be that dramatically worse that we should require such magic or
> constraints than a regular type without all the magic and alignment
> requirements?
>
> Not speaking for the above proposal, but in general, it is currently
> believed that using something like the CPU carry bit ought to be zero
> overhead on most CPUs, whereas a discriminant stored in the layout would
> not be zero overhead on more CPUs.

For some cpus, there are cheap flags like this. (Some cpus even have
"return with carry flag set" and "return with carry flag clear"
instructions that are tailor-made for such usage.) On many bigger cpus,
however, working with flags can cause stalls in the processor pipelines
- the flag becomes a bottleneck dependency. It is precisely for that
reason that RISC-V and some other newer designs have no flags.

Niall Douglas

unread,
Jul 24, 2018, 6:38:01 AM7/24/18
to ISO C++ Standard - Future Proposals
The pipeline bubbles you refer to only occur when testing more than one CPU flag at a time. One of the main reasons for choosing the carry bit as return discriminant is because there are instructions for testing just the carry bit alone on all such architectures. Thus such partial flags stalls will never occur in this specific use case.

Niall

David Brown

unread,
Jul 24, 2018, 9:16:56 AM7/24/18
to std-pr...@isocpp.org
I am reluctant to be so confident about the effects here - I really
don't think you can generalise and say these stalls will never occur. I
think it will depend a lot on the cpu ISA, the details of its
implementation (scheduling, out-of-order execution, register renaming,
pipelining, speculative execution, etc.), and the type of code you are
dealing with. On many cpus, there are quite a lot of instructions that
use or affect the carry flag (or any other flags) - these pose a
challenge for scheduling. They also pose a challenge for the compiler,
if it would want to use such instructions between the call to "foo" and
the check for "foo.error".

It may be fair to say that using the carry flag here would not cause
much in the way of stalling or delays - but I would not say "never"
without a lot more thorough analysis with a range of different processors.

On a related point, many compilers do not track the state of flags
across instruction pattern boundaries - /any/ use of a carry flag like
this would involve significant engineering effort.


Nicol Bolas

unread,
Jul 24, 2018, 10:40:39 AM7/24/18
to ISO C++ Standard - Future Proposals
I think the design of this proposal is intended to be much more conservative than Niall's. In particular, it is purely focused on the transmission of error codes via this invisible fail state. That is, it's a mere optimization of what you would otherwise have written in an error-code-based world.

If your hypothetical `foo` function had returned an error code, then there's no way for that `foo(y) + foo(z)` code to work. Therefore, error-code-based functions wouldn't have been written that way, so functions under the new paradigm also wouldn't be written this way.

Now personally, I think that's basically throwing away a golden opportunity to more reasonably handle errors in C. But that is the basic thinking behind the proposal; that kind of compatibility is exactly what it's going for. The idea is that this is merely about how you transmit the information you would have, rather than how you process it.

Jake Arkinstall

unread,
Jul 24, 2018, 11:41:45 AM7/24/18
to std-pr...@isocpp.org
On Tue, 24 Jul 2018, 05:43 Nicol Bolas, <jmck...@gmail.com> wrote:
On Monday, July 23, 2018 at 9:55:11 PM UTC-4, Jake Arkinstall wrote:
Error handling via an effective static state isn't thread safe. Marking the flag as volatile and telling users to check for errors immediately after the function call goes some way to help, I guess, but it's not sufficient to guarantee that errors won't crop up in the wrong thread (or do I have some reading to do?)

I think this is a misunderstanding of what the paper is saying. It is not talking about "static state" (even though it appears to). The `funcName.failed` bit is not intended to be a static property of `funcName`; it's closer to an invisible stack variable, which you access by using the function's name. The compiler turns "funcName.failed" into the specific stack location or register or whatever that maps to that data.

Well, that leaves me with egg on my face. The proposal certainly makes more sense given what you've said. On a similar note, then, what would this do to our constexpr?

Nicol Bolas

unread,
Jul 24, 2018, 11:58:23 AM7/24/18
to ISO C++ Standard - Future Proposals
It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

Niall Douglas

unread,
Jul 24, 2018, 1:52:21 PM7/24/18
to ISO C++ Standard - Future Proposals
Well, that leaves me with egg on my face. The proposal certainly makes more sense given what you've said. On a similar note, then, what would this do to our constexpr?

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr, as deterministic exceptions is ordinary control flow.

I appreciate that will be a controversial statement. However I've been working in this area closely for a long time now, and I am afraid that is ordinary control flow. This says nothing about whether one ought to use it as control flow, just that it is control flow in a way unlike type based exception throws.

Besides, I intend for P1031 Low level file i/o to work in constexpr as soon as I can persuade the committee, and being that it fails via deterministic exceptions, that would also need to work in constexpr.

Niall 

Nicol Bolas

unread,
Jul 24, 2018, 2:36:02 PM7/24/18
to ISO C++ Standard - Future Proposals
On Tuesday, July 24, 2018 at 1:52:21 PM UTC-4, Niall Douglas wrote:
Well, that leaves me with egg on my face. The proposal certainly makes more sense given what you've said. On a similar note, then, what would this do to our constexpr?

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

Thiago Macieira

unread,
Jul 24, 2018, 3:28:03 PM7/24/18
to std-pr...@isocpp.org
On Tuesday, 24 July 2018 06:16:42 PDT David Brown wrote:
> I am reluctant to be so confident about the effects here - I really
> don't think you can generalise and say these stalls will never occur. I
> think it will depend a lot on the cpu ISA, the details of its
> implementation (scheduling, out-of-order execution, register renaming,
> pipelining, speculative execution, etc.), and the type of code you are
> dealing with. On many cpus, there are quite a lot of instructions that
> use or affect the carry flag (or any other flags) - these pose a
> challenge for scheduling. They also pose a challenge for the compiler,
> if it would want to use such instructions between the call to "foo" and
> the check for "foo.error".
>
> It may be fair to say that using the carry flag here would not cause
> much in the way of stalling or delays - but I would not say "never"
> without a lot more thorough analysis with a range of different processors.

That's entirely QoI. If the compiler that targets this particular system where
setting or testing a particular flag is slow, the compiler can simply choose
another way of returning the information.

Let's take another example of a CPU without flags: IA-64. Instead of having
flags, the CPU has 1-bit predicate registers, which are set or unset depending
on the comparison operation that was performed. So IA-64 could then use one of
the scratch predicate registers as an extra return value. There are 10 of
them.

Another example is MIPS: it has no carry flag and no predicate register. In
that case, it would need to use an extra register (currently used for scratch)
with either a zero or non-zero value, which would allow the caller to use the
BNEZ instruction to jump to the failure path. Fortunately, MIPS has plenty of
registers, so it can do that.

This is up to the ABI documents for each platform to determine.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center



inkwizyt...@gmail.com

unread,
Jul 24, 2018, 6:55:55 PM7/24/18
to ISO C++ Standard - Future Proposals
Why not? C++ inhered multiple things from C, this could be another one. If Niall define this functionality in compatible way for C++ then we need only add one paragraph that "behavior of C exceptions from C2x work same way in C++2y" and another one how glue both. I would even go further and build C++ exception on top of C as syntax sugar but this probably is not direction that Niall is going.

Nicol Bolas

unread,
Jul 24, 2018, 9:24:44 PM7/24/18
to ISO C++ Standard - Future Proposals
On Tuesday, July 24, 2018 at 6:55:55 PM UTC-4, Marcin Jaczewski wrote:
On Tuesday, July 24, 2018 at 8:36:02 PM UTC+2, Nicol Bolas wrote:
On Tuesday, July 24, 2018 at 1:52:21 PM UTC-4, Niall Douglas wrote:
Well, that leaves me with egg on my face. The proposal certainly makes more sense given what you've said. On a similar note, then, what would this do to our constexpr?

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

Why not?

Because P0709 is superior in pretty much every way. From the fixed, simple, and highly flexible error type to making error handling control flow distinct from regular code flow, and with plenty of points of extension besides (such as my suggestion for allowing `try` to unpack ValueOrError types into exceptions).

I just don't see any reason to use the C mechanism. Doubly so because it is only compatible if you choose to make it compatible (by using `_Fails` with the `std::error`-equivalent object). So why have an option that people shouldn't be using?

C++ inhered multiple things from C, this could be another one.

And C++ has not inherited multiple other things from C; this could be another one. The `_Fails()` mechanism is a perfectly fine C-ism. But it is not a C++ solution to the problem, and we should not encourage it by canonizing it for our language.

They may share the underlying ABI, similar substructure for communicating between functions. And there could even be some declaration in the C++ standard which says that an `extern "C"` function declared with `throws` shall be equivalent in some way to a C function with `_Fails(cxx_error)` or whatever.

But having `_Fails()` be legal C++? No. The point of giving C `_Fails()` from our perspective is so that C++ static exceptions can at least pass through C APIs.

David Brown

unread,
Jul 25, 2018, 3:04:09 AM7/25/18
to std-pr...@isocpp.org
On 24/07/18 21:27, Thiago Macieira wrote:
> On Tuesday, 24 July 2018 06:16:42 PDT David Brown wrote:
>> I am reluctant to be so confident about the effects here - I really
>> don't think you can generalise and say these stalls will never occur. I
>> think it will depend a lot on the cpu ISA, the details of its
>> implementation (scheduling, out-of-order execution, register renaming,
>> pipelining, speculative execution, etc.), and the type of code you are
>> dealing with. On many cpus, there are quite a lot of instructions that
>> use or affect the carry flag (or any other flags) - these pose a
>> challenge for scheduling. They also pose a challenge for the compiler,
>> if it would want to use such instructions between the call to "foo" and
>> the check for "foo.error".
>>
>> It may be fair to say that using the carry flag here would not cause
>> much in the way of stalling or delays - but I would not say "never"
>> without a lot more thorough analysis with a range of different processors.
>
> That's entirely QoI. If the compiler that targets this particular system where
> setting or testing a particular flag is slow, the compiler can simply choose
> another way of returning the information.

Exactly. Well, /almost/ exactly - it could be a problem for ISA's where
the cost of using a carry flag is cheap (cheaper than using a standard
bool in a register) on some implementations and costly in other
implementations, since they need to share the same standardised ABI. (I
am thinking primarily of the x86 world here, where there is a huge
variation in the implementation details.)

This would be easier if it really was the compiler that could make the
choices here - but the proposal is for an ABI change, not just an
internal compiler choice.

>
> Let's take another example of a CPU without flags: IA-64. Instead of having
> flags, the CPU has 1-bit predicate registers, which are set or unset depending
> on the comparison operation that was performed. So IA-64 could then use one of
> the scratch predicate registers as an extra return value. There are 10 of
> them.
>
> Another example is MIPS: it has no carry flag and no predicate register. In
> that case, it would need to use an extra register (currently used for scratch)
> with either a zero or non-zero value, which would allow the caller to use the
> BNEZ instruction to jump to the failure path. Fortunately, MIPS has plenty of
> registers, so it can do that.
>
> This is up to the ABI documents for each platform to determine.
>

Indeed.

My point was merely that using a carry flag would not necessarily be
"zero overhead" even on cpus that have a carry flag. For different
cpus, different solutions would be more efficient.

All in all, I am sceptical to the idea that using a carry flag would be
a big enough win on any platform (except, perhaps, on some small
microcontrollers) to be worth the massive complications involved. If
the whole concept were handled using a regular C struct and union with a
bool member, then it would work already with existing tools and ABI's -
it is merely syntactic sugar in the compiler front-end, rather than a
major change throughout the chain.

(Of course, there is nothing wrong with defining an ABI to say that a
"bool" return, either in a small struct or stand-alone, is returned in a
flag or predicate register - just as the ABI defines register choices
for different sized integers and floats in returns. Then the benefits
would be seen on all functions returning a bool, not just these proposed
exceptions.)


David Brown

unread,
Jul 25, 2018, 3:33:19 AM7/25/18
to std-pr...@isocpp.org
On 24/07/18 16:40, Nicol Bolas wrote:
>
>
> On Tuesday, July 24, 2018 at 5:08:45 AM UTC-4, David Brown wrote:
>
> On 24/07/18 10:25, Gašper Ažman wrote:
> > If we are to use the function's name, how do we deal with unutterable
> > functions, like lambdas, or methods? You can dismiss it as "right, C
> > doesn't have those", but they might in the future. I don't think
> it's a
> > good idea at all.
> >
> > I also don't like that if I rename the function, I now have to change
> > its body. That makes very little sense to me. A rose by any other
> name
> > *should* smell as sweet.
> >
>
> Having programmed in Pascal, where you use the function's name as the
> return value pseudo-variable, I agree with that - it is a pain.
>
> It may also be a challenge for recursive functions, and it will
> certainly be a problem if you want to use the function more than once
> before checking for errors:
>
> x = foo(y) + foo(z);
> if (foo.error) ... // Which foo call?
>
> I appreciate the desire for cheap and standardised error handling, but
> this syntax is not going to work.
>
>
> I think the design of this proposal is intended to be much more
> conservative than Niall's. In particular, it is /purely/ focused on the
> transmission of error codes via this invisible fail state. That is, it's
> a mere optimization of what you would otherwise have written in an
> error-code-based world.
>
> If your hypothetical `foo` function had returned an error code, then
> there's no way for that `foo(y) + foo(z)` code to work. Therefore,
> error-code-based functions wouldn't have been written that way, so
> functions under the new paradigm also wouldn't be written this way.

There are currently two main ways of handling errors in C++.

You can use C-style status returns. In that case, "foo(y) + foo(z)"
does not work. But you /know/ it does not work - it is very clear from
the way the functions are declared and used.

Or you can use exceptions. Then "foo(y) + foo(z)" /does/ work, and it
is clear that it works.

With this proposal, it looks like "foo(y) + foo(z)" should work, but it
does not work. And therein lies the danger.

Niall Douglas

unread,
Jul 25, 2018, 4:14:33 AM7/25/18
to ISO C++ Standard - Future Proposals

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

I appreciate you are probably not up to date, as I have not issued a new draft of  D1095 which incorporates the substantial feedback I received from WG14. And I have only very briefly summarised what will go into that draft on std-proposals and /r/cpp/.

But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T). The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++. I intend to propose, in fact, that both mechanisms work perfectly in constexpr. It would let constexpr code choose whether failure should be handled explicitly (_Fails) or implicitly (throws), exactly the same as in non-constexpr.

Niall

Niall Douglas

unread,
Jul 25, 2018, 4:25:10 AM7/25/18
to ISO C++ Standard - Future Proposals
On Tuesday, July 24, 2018 at 2:16:56 PM UTC+1, David Brown wrote:
On 24/07/18 12:38, Niall Douglas wrote:
> The pipeline bubbles you refer to only occur when testing more than
> one CPU flag at a time. One of the main reasons for choosing the
> carry bit as return discriminant is because there are instructions
> for testing just the carry bit alone on all such architectures. Thus
> such partial flags stalls will never occur in this specific use
> case.
>

I am reluctant to be so confident about the effects here - I really
don't think you can generalise and say these stalls will never occur.

Remember we only care about the specific case of where an extern, non-inlined function returns. On every architecture, that would involve causing the carry flag to be set or be cleared, executing a return from subroutine, and in the caller doing a branch if carry set or clear.

When researching all this some years ago now, I studied x64, ARM and AArch64, both in order and out of order variants. On those architectures - which represent a good fraction of all CPUs likely to be in use in five years time - I found between zero and two CPU cycle overhead, assuming all warm cache. The out of order cores were almost always zero overhead as the code after the return from function is speculatively executed.
 

On a related point, many compilers do not track the state of flags
across instruction pattern boundaries - /any/ use of a carry flag like
this would involve significant engineering effort.

Again, we only care about calls to non-inlined extern functions. In all other cases, any use of the carry flag will be optimised out. So in fact the proposed mechanism is purely about calling convention of extern functions only, and should require very little work on the compiler because for calls of extern functions, no optimisation is possible in its calling convention anyway.

Niall 

Niall Douglas

unread,
Jul 25, 2018, 4:26:51 AM7/25/18
to ISO C++ Standard - Future Proposals
On Tuesday, July 24, 2018 at 2:16:56 PM UTC+1, David Brown wrote:
On 24/07/18 12:38, Niall Douglas wrote:
> The pipeline bubbles you refer to only occur when testing more than
> one CPU flag at a time. One of the main reasons for choosing the
> carry bit as return discriminant is because there are instructions
> for testing just the carry bit alone on all such architectures. Thus
> such partial flags stalls will never occur in this specific use
> case.
>

I am reluctant to be so confident about the effects here - I really
don't think you can generalise and say these stalls will never occur.

Remember we only care about the specific case of where an extern, non-inlined function returns. On every architecture, that would involve causing the carry flag to be set or be cleared (or equivalent in TLS), executing a return from subroutine, and in the caller doing a branch if carry set or clear.

When researching all this some years ago now, I studied x64, ARM and AArch64, both in order and out of order variants. On those architectures - which represent a good fraction of all CPUs likely to be in use in five years time - I found between zero and three CPU cycle overhead, assuming all warm cache. The out of order cores were almost always zero overhead as the code after the return from function is speculatively executed.
 

On a related point, many compilers do not track the state of flags
across instruction pattern boundaries - /any/ use of a carry flag like
this would involve significant engineering effort.

Nicol Bolas

unread,
Jul 25, 2018, 11:24:19 AM7/25/18
to ISO C++ Standard - Future Proposals
On Wednesday, July 25, 2018 at 4:14:33 AM UTC-4, Niall Douglas wrote:

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

I appreciate you are probably not up to date, as I have not issued a new draft of  D1095 which incorporates the substantial feedback I received from WG14. And I have only very briefly summarised what will go into that draft on std-proposals and /r/cpp/.

But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T).

Pedantic note: `throws(T)` doesn't exist. `throws(<expr>)` is a conditional throws declaration, much like `noexcept(<expr>)`. P0709 makes that abundantly clear.

What you're looking for is `throws{T}`. Which P0709 treats as an extension, not a required part of the proposal. So there's no guarantee it will make it into any hypothetical C++23.

The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++.

That's not how C++ has worked since 1998. C++11 did not incorporate C99 wholesale; it only took specific things from it. Similarly, C++14/17/20 did not adopt C11 wholesale. The two languages are diverging.

So why should we expect C++23 to adopt C22 in this way?

inkwizyt...@gmail.com

unread,
Jul 25, 2018, 12:43:54 PM7/25/18
to ISO C++ Standard - Future Proposals


On Wednesday, July 25, 2018 at 5:24:19 PM UTC+2, Nicol Bolas wrote:
On Wednesday, July 25, 2018 at 4:14:33 AM UTC-4, Niall Douglas wrote:

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

I appreciate you are probably not up to date, as I have not issued a new draft of  D1095 which incorporates the substantial feedback I received from WG14. And I have only very briefly summarised what will go into that draft on std-proposals and /r/cpp/.

But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T).

Pedantic note: `throws(T)` doesn't exist. `throws(<expr>)` is a conditional throws declaration, much like `noexcept(<expr>)`. P0709 makes that abundantly clear.

What you're looking for is `throws{T}`. Which P0709 treats as an extension, not a required part of the proposal. So there's no guarantee it will make it into any hypothetical C++23.

The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++.

That's not how C++ has worked since 1998. C++11 did not incorporate C99 wholesale; it only took specific things from it. Similarly, C++14/17/20 did not adopt C11 wholesale. The two languages are diverging.

So why should we expect C++23 to adopt C22 in this way?

 
To make easier to share code between? Image big C22 library that use this new calling convention in headers, if we accept this part of C22 using this library in C++23 will be trivial. This is too other way around, it allow C++ to define headers that will be easy consumable by C and other languages that will support C22 exceptions (this is major reason).

Nicol Bolas

unread,
Jul 25, 2018, 1:07:26 PM7/25/18
to ISO C++ Standard - Future Proposals
On Wednesday, July 25, 2018 at 12:43:54 PM UTC-4, Marcin Jaczewski wrote:
On Wednesday, July 25, 2018 at 5:24:19 PM UTC+2, Nicol Bolas wrote:
On Wednesday, July 25, 2018 at 4:14:33 AM UTC-4, Niall Douglas wrote:

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

I appreciate you are probably not up to date, as I have not issued a new draft of  D1095 which incorporates the substantial feedback I received from WG14. And I have only very briefly summarised what will go into that draft on std-proposals and /r/cpp/.

But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T).

Pedantic note: `throws(T)` doesn't exist. `throws(<expr>)` is a conditional throws declaration, much like `noexcept(<expr>)`. P0709 makes that abundantly clear.

What you're looking for is `throws{T}`. Which P0709 treats as an extension, not a required part of the proposal. So there's no guarantee it will make it into any hypothetical C++23.

The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++.

That's not how C++ has worked since 1998. C++11 did not incorporate C99 wholesale; it only took specific things from it. Similarly, C++14/17/20 did not adopt C11 wholesale. The two languages are diverging.

So why should we expect C++23 to adopt C22 in this way?

 
To make easier to share code between? Image big C22 library that use this new calling convention in headers, if we accept this part of C22 using this library in C++23 will be trivial.

Define "using this library in C++23". Do you mean compiling it as C++? Or linking with it in C++? If it's compiling as C++, then that library's writer would still have to limit themselves to the subset of C22 that C++ supports.

If you're talking about ABI interop, we don't need C++ to be able to read C22 to have that. ABIs can allow a C++ `throws` function that is declared `extern "C"` to be ABI-equivalent to a `_Fails(cxx_error)` or whatever. Yes, there would have to be two versions of the header to make that work.

However, if C++ consuming C headers directly is an important thing, then C++ would still not have to include the entire mechanism. We would simply declare that `_Fails()` is a legal thing, but that you can only fail with `cxx_error` as the type, and that such a function's declaration is exactly equivalent to a function declared with `throws`. And that's it. C uses its mechanism on its side, and C++ uses its mechanism on its side.

The problem of course is that C users may not want to use our error type. And if they don't, that limits interoperation drastically.

This is too other way around, it allow C++ to define headers that will be easy consumable by C and other languages that will support C22 exceptions (this is major reason).

Such "C++ headers" would just be C22 headers, since they couldn't contain any actual C++.

inkwizyt...@gmail.com

unread,
Jul 25, 2018, 2:27:13 PM7/25/18
to ISO C++ Standard - Future Proposals


On Wednesday, July 25, 2018 at 7:07:26 PM UTC+2, Nicol Bolas wrote:
On Wednesday, July 25, 2018 at 12:43:54 PM UTC-4, Marcin Jaczewski wrote:
On Wednesday, July 25, 2018 at 5:24:19 PM UTC+2, Nicol Bolas wrote:
On Wednesday, July 25, 2018 at 4:14:33 AM UTC-4, Niall Douglas wrote:

It's a C proposal; it doesn't interact with `constexpr`. Indeed, one of its biggest flaws is that it doesn't allow for inter-operation with C++ static exceptions at all, despite being in part based on the same principle.

My _Fails() proposal does however work in constexpr,

`_Fails()` is not valid C++, and `constexpr` is not valid C. How can they work with one another? Unless you're proposing to add `_Fails()` to C++, which I think would be a rather hard sell.

I appreciate you are probably not up to date, as I have not issued a new draft of  D1095 which incorporates the substantial feedback I received from WG14. And I have only very briefly summarised what will go into that draft on std-proposals and /r/cpp/.

But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T).

Pedantic note: `throws(T)` doesn't exist. `throws(<expr>)` is a conditional throws declaration, much like `noexcept(<expr>)`. P0709 makes that abundantly clear.

What you're looking for is `throws{T}`. Which P0709 treats as an extension, not a required part of the proposal. So there's no guarantee it will make it into any hypothetical C++23.

The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++.

That's not how C++ has worked since 1998. C++11 did not incorporate C99 wholesale; it only took specific things from it. Similarly, C++14/17/20 did not adopt C11 wholesale. The two languages are diverging.

So why should we expect C++23 to adopt C22 in this way?

 
To make easier to share code between? Image big C22 library that use this new calling convention in headers, if we accept this part of C22 using this library in C++23 will be trivial.

Define "using this library in C++23". Do you mean compiling it as C++? Or linking with it in C++? If it's compiling as C++, then that library's writer would still have to limit themselves to the subset of C22 that C++ supports.


Yes, and whole point is to expand things that are shared between C and C++ that more libs are compatible even if authors do not focus on this.
 
If you're talking about ABI interop, we don't need C++ to be able to read C22 to have that. ABIs can allow a C++ `throws` function that is declared `extern "C"` to be ABI-equivalent to a `_Fails(cxx_error)` or whatever. Yes, there would have to be two versions of the header to make that work.

However, if C++ consuming C headers directly is an important thing, then C++ would still not have to include the entire mechanism. We would simply declare that `_Fails()` is a legal thing, but that you can only fail with `cxx_error` as the type, and that such a function's declaration is exactly equivalent to a function declared with `throws`. And that's it. C uses its mechanism on its side, and C++ uses its mechanism on its side.


Agree, this could this could work too.
 
The problem of course is that C users may not want to use our error type. And if they don't, that limits interoperation drastically.


This depend on exactly version will be added to C++, if we have only `std::error` then yes, if not and C++ support `throws{T}` then we could handle more types.
But this will need some conversion because it would be very hard to have `_Fails(T)` and `throws{T}` for same type. Reason is different requirement for both versions.
From previous discussion I could see this avoided by using different related types like `_Fails(T::exception_payload)` and `throws{T}`.
If this new exception would be implemented in way I see then this will not be big problem but if another way will be choose then this indeed become problematic to easy share headers.
 
This is too other way around, it allow C++ to define headers that will be easy consumable by C and other languages that will support C22 exceptions (this is major reason).

Such "C++ headers" would just be C22 headers, since they couldn't contain any actual C++.

Right

Niall Douglas

unread,
Jul 25, 2018, 3:47:03 PM7/25/18
to ISO C++ Standard - Future Proposals
But in short, C22 _Fails(T) maps directly onto C++ 23 throws(T).

Pedantic note: `throws(T)` doesn't exist. `throws(<expr>)` is a conditional throws declaration, much like `noexcept(<expr>)`. P0709 makes that abundantly clear.

What you're looking for is `throws{T}`. Which P0709 treats as an extension, not a required part of the proposal. So there's no guarantee it will make it into any hypothetical C++23.

There may be some movement on that next revision of P0709 in order to ease things for WG14.
 

The only difference is auto-propagation, so a _Fails(T) if unhandled results in a failure to compile, whereas a throws(T) if unhandled propagates upwards.

This is what WG14 asked for. As C++ 23 would surely be incorporating C22 into itself, whatever goes into C also goes in C++.

That's not how C++ has worked since 1998. C++11 did not incorporate C99 wholesale; it only took specific things from it. Similarly, C++14/17/20 did not adopt C11 wholesale. The two languages are diverging.

So why should we expect C++23 to adopt C22 in this way?

Both convenors of WG14 and WG21 want coevolution on this feature, especially as it makes it much easier to bring C++ contracts into C. That doesn't mean it will happen of course. But it does mean that right now, that's the aim, until either committee conclusively decides otherwise.

Niall

Gašper Ažman

unread,
Jul 26, 2018, 5:24:39 AM7/26/18
to std-pr...@isocpp.org
Niall,

Do you have an updated version of your mechanism paper? Among my colleagues, there is a lot of informal interest that Herb's paper just doesn't address - and your paper is really good for shutting down FUD.

If you do, could you ping it through here?

Thanks,

Gašper

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Niall Douglas

unread,
Jul 26, 2018, 1:51:56 PM7/26/18
to ISO C++ Standard - Future Proposals

Do you have an updated version of your mechanism paper? Among my colleagues, there is a lot of informal interest that Herb's paper just doesn't address - and your paper is really good for shutting down FUD.

If you do, could you ping it through here?

Only the drafts posted to std-proposals so far.

I aim to have the next draft of said papers ready for the August SG14 telecon, which is mid-August.

Final paper will need to be ready for the September WG14 mailing deadline or the October WG21 mailing deadline.

They'll all be posted to std-proposals beforehand, so watch this forum.

Niall 
Reply all
Reply to author
Forward
0 new messages