In a relatively recent thread, there was a discussion of functions with return types when not all control paths return a value. This is occasionally useful if the function will never fall off the end, and it's impossible for the compiler to get this right at runtime. So the standard does not require erroring out if the compiler statically detects a path of execution that could lead to falling off the end.
It would be a good idea for static analysis tools to be able to find cases where you accidentally fail to return. So I suggest adding the ability to explicitly say "Yes, I meant to not return something here". This would be something like [[unreachable]], where the compiler/tool assumes that the line in question will never be reached. That way, static analysis tools/compilers can warn on not seeing a return statement, but you can silence the warning when you actually meant to do it.
--
---
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-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
[I'm aware of function-like intrinsics such as
__builtin_unreachable(), that seem to realize what you want to
describe in a portable fashion, but these are not attributes].
Unless I misunderstood your requet completely, I have the feeling that
an attribute might not be the best way to realize that. Why couldn't
that be a standard library function instead?
- Daniel
I've thought about proposing something like this before.Let us call this an unreachable statement and put aside whether or not it is an attribute statement or a statement that has a semantic effect for the moment.Let us say that the intent of an unreachable statement is to assert that the statement in question shall never be executed.
I am a very big fan of attributes (my team wrote almost 10 gcc extensions based on them, successfully improving static analysis on real life mission critical code).
However, in this case I do prefer Andrews's suggestion, because: 1) as he suggests, this may (or may not) have runtime comsequences (we could even have an unreacheable_handler() with its setter, a la uncaught_handler) enabling runtime consistency checks and even allowing the user to decide weather to abort() or take a different action; and 2) this is perfectly understandable by static checkers (what's the difference in a checker's parser to handle a std function call and an attribute?).
Sure, I think the point is that if we made it a blessed standard library function (instead of an attribute) and you wrote:double convert(values v) noexcept
{
switch (v)
{
case one:
return 1.0;
case two:
return 2.0;
}std::unreachable();
}You would:1. Get no warning. (like an attribute)2. In the case someone calls convert(values(one | two)), you would get predictable (perhaps even configurable) behavior, and not undefined behavior.For no-default enum switch instances like this, we also need to consider the following use case:1. A new enumerator is added to the enumeration definition (eg three).2. The switch has been forgotten to be updated.
> But that's not the intent of what I was wanting. All I want is a marker to tell compilers/static tools that "I really did mean to not return here." That's it. Anything more is scope creep.
I am a very big fan of attributes (my team wrote almost 10 gcc extensions based on them, successfully improving static analysis on real life mission critical code).
However, in this case I do prefer Andrews's suggestion, because: 1) as he suggests, this may (or may not) have runtime comsequences (we could even have an unreacheable_handler() with its setter, a la uncaught_handler) enabling runtime consistency checks and even allowing the user to decide weather to abort() or take a different action; and 2) this is perfectly understandable by static checkers (what's the difference in a checker's parser to handle a std function call and an attribute?).
Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
On 9 November 2015 at 16:32, Myriachan <myri...@gmail.com> wrote:Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
That definition sounds like a semantic effect to me...
On 9 November 2015 at 16:32, Myriachan <myri...@gmail.com> wrote:Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
That definition sounds like a semantic effect to me...
If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined.
On Mon, Nov 9, 2015 at 11:51 PM, Nevin Liber <ne...@eviloverlord.com> wrote:On 9 November 2015 at 16:32, Myriachan <myri...@gmail.com> wrote:Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
That definition sounds like a semantic effect to me...[[noreturn]] causes undefined behavior. Some consider undefined behavior a semantic effect, some don't.
On Mon, Nov 9, 2015 at 11:51 PM, Nevin Liber <ne...@eviloverlord.com> wrote:On 9 November 2015 at 16:32, Myriachan <myri...@gmail.com> wrote:Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
That definition sounds like a semantic effect to me...[[noreturn]] causes undefined behavior. Some consider undefined behavior a semantic effect, some don't.
(Having said that, I still don't see the benefit of a UB [[unreachable]] attribute statement over having an std::unreachable() statement causing some well-defined runtime error.)
switch (day)
{
case Days::SATURDAY:
case Days::SUNDAY:
Weekend();
break;
case Days::MONDAY:
case Days::TUESDAY:
case Days::WEDNESDAY:
case Days::THURSDAY:
case Days::FRIDAY:
Weekday();
break;
default:
[[unreachable]];
}
mov eax, [rcx + 12]
cmp eax, 7
jae call_error_function
lea rdx, [rel jump_table]
jmp qword [rdx + rax * 8]On 9 November 2015 at 16:32, Myriachan <myri...@gmail.com> wrote:Essentially, [[unreachable]] would be defined as "the behavior of executing a statement containing the [[unreachable]] attribute is undefined".
That definition sounds like a semantic effect to me...
Throwing an exception or calling std::terminate() are covered by this option, which could default to call terminate.
Again, nothing prevents static analyzers and even optimizers to do the same analysis whereas this is an attribute or a specific function call.
> - Configurably one of the above.
> - Something else?
>
El 9/11/2015 22:12, "Andrew Tomazos" <andrew...@gmail.com> escribió:
>
>
>
> On Tue, Nov 10, 2015 at 1:43 AM, Andrey Semashev <andrey....@gmail.com> wrote:
>>
>> On Tuesday, November 10, 2015 12:17:44 AM Andrew Tomazos wrote:
>> > On Tue, Nov 10, 2015 at 12:09 AM, Andrey Semashev <andrey....@gmail.com
>> > >The compiler would then be
>> > > able to optimize the code better as it would be able to remove the checks
>> > > and
>> > > branching instructions for these cases altogether. I suspect that would
>> > > not be
>> > > possible with std::unreachable().
>> >
>> > A std::unreachable() standard library call can necessarily do everything
>> > that an attribute statement does and more. So, no it is possible.
>>
>
> Options are:
>
> - Nothing. The statement is a no-op.
> - Undefined behavior.
> - An exception is thrown.
> - std::terminate is called.
> - A user-defined handler is called.
Throwing an exception or calling std::terminate() are covered by this option, which could default to call terminate.Again, nothing prevents static analyzers and even optimizers to do the same analysis whereas this is an attribute or a specific function call.
> - Configurably one of the above.
> - Something else?
>
switch (day)
{
case Days::SATURDAY:
case Days::SUNDAY:
Weekend();
break;
case Days::MONDAY:
case Days::TUESDAY:
case Days::WEDNESDAY:
case Days::THURSDAY:
case Days::FRIDAY:
Weekday();
break;
default:
std::unreachable();
}
On Tue, Nov 10, 2015 at 1:45 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Tuesday, November 10, 2015 at 4:24:45 PM UTC-5, Andrew Tomazos wrote:If we pick the configurable option, and the configuration is made at compile-time, one of the configuration options could result in undefined behavior (and encourage optimizing the code out). For example they may want this behavior in release build, but in debug build they want to get a handler called (that prints a stack trace say and then gracefully crashes).It's hard to get excited about the optimization, though. We're talking one (unlikely-marked) branch and a few bytes of code size.Getting rid of undefined behavior on the other hand, has a whole SG dedicated to it.
Here's the thing: there's no such thing in the C++ standard as a "configurable option". There are only 3 options: standard-defined behavior, implementation-defined behavior, and un-defined behavior. All those compiler switches you have? Those either deal with something outside of the standard, define how the compiler deals with implementation/undefined behavior, or specifies behavior that is against what the standard requires.
In all cases, they're not something that is a part of the standard. So the best thing the standard can do is say "undefined behavior", thus allowing specific implementations to decide whether to define it or not.
I would have agreed with you prior to Kona, but contracts are basically going down the configurable route [...].
On 2015–11–11, at 8:04 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:Interesting. A possible definition of std::unreachable() would be an empty function whose precondition is always false. The problem of defining its behavior if is actually invoked would be part of the general handling of contracts.
On 2015–11–11, at 8:04 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:Interesting. A possible definition of std::unreachable() would be an empty function whose precondition is always false. The problem of defining its behavior if is actually invoked would be part of the general handling of contracts.The contract is an attribute in Ed’s example because the consensus is that UB on precondition failure doesn’t alter semantics, right? So, a language extension for contracts isn’t needed for [[unreachable]].
It would still be nice to spell unreachable as an attribute-statement as opposed to a function, even if it’s not magic. It acts like a language construct. Inside switch, it’s also an alternative to [[fallthrough]].
On Wednesday, November 11, 2015 at 3:36:44 PM UTC, Jonas Persson wrote:On Wed, Nov 11, 2015 at 4:09 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
On Wednesday, November 11, 2015 at 1:51:22 PM UTC, David Krauss wrote:On 2015–11–11, at 8:04 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:Interesting. A possible definition of std::unreachable() would be an empty function whose precondition is always false. The problem of defining its behavior if is actually invoked would be part of the general handling of contracts.The contract is an attribute in Ed’s example because the consensus is that UB on precondition failure doesn’t alter semantics, right? So, a language extension for contracts isn’t needed for [[unreachable]].
if that's the consensus for contracts, then yes, there is no need for explicit support for unreachable.
It would still be nice to spell unreachable as an attribute-statement as opposed to a function, even if it’s not magic. It acts like a language construct. Inside switch, it’s also an alternative to [[fallthrough]].
why though? Wouldn't an std::unreachable() work just fine in your example? I don't have strong preference either way, but wouldn't a function call be more idiomatic than an attribute attached to an empty statement?
-- gpdThis can't be how contracts work. That would be very confusing.If the compiler figure out that a precondition is always false it has to either fail to compile or invoke the violation handler.It can use the preconditions to optimize inside the function, but not silently optimize away the code on the caller side as we would expect [[unreachable]] to do.
Assuming that the effect contract violations can be configured as UB, then it is just another source of UB. Compilers already optimize today by backpropagating UB, std::unreachable wouldn't be any different.
If you prefer an exception or an handler to be called on precondition violation, you probably want that on a reached std::unreachable as well.
Also the compiler (or static analyser) should complain only if it can statically prove that a function with invalid preconditions is actually called.
-- gpd
/ Jonas
So in the case of std::unreachable it is not allowed to complain since it cannot prove it will be called, but is is allowed to optimize as if it is not, even if that cannot be proved?I'm not 100% sure I understand what you are saying, but I think my answer is:
Yes, exactly.The point of facilities like this is for you, the developer, to tell the compiler things that it can't (with current technology) prove on its own.It is your way of saying "trust me, even if you can't prove this, I can". So optimize as if it was proven.Then on the flip side, maybe in debug, ie no optimizations, we want to say "but wait, if it turns out I was wrong, scream and yell at runtime".TonyI agree that is a thing we need. What I am objecting to is using a funtction with a precondition to achive this. A precondition from the callers perspective is something that should be checked, not a thruth.For that it would be better to have something designed for the task, like __assume(expr)
So in the case of std::unreachable it is not allowed to complain since it cannot prove it will be called, but is is allowed to optimize as if it is not, even if that cannot be proved?I'm not 100% sure I understand what you are saying, but I think my answer is:
Yes, exactly.The point of facilities like this is for you, the developer, to tell the compiler things that it can't (with current technology) prove on its own.It is your way of saying "trust me, even if you can't prove this, I can". So optimize as if it was proven.Then on the flip side, maybe in debug, ie no optimizations, we want to say "but wait, if it turns out I was wrong, scream and yell at runtime".TonyI agree that is a thing we need. What I am objecting to is using a funtction with a precondition to achive this. A precondition from the callers perspective is something that should be checked, not a thruth.For that it would be better to have something designed for the task, like __assume(expr)Let's say the precondition on sqrt(x) is x >= 0. So for this (bad contrived example) code:if (x >= 0) {do_stuff();} else {x = sqrt(x);do_other_stuff();
}Shouldn't the compiler be able to remove the else block completely? (Maybe not in debug, but at least in some "max optimize" mode where it assumes preconditions mean UB and thus never happen?)
Any UB from not fulfilling the precondition should only happen inside the called function.We would gain nothing from keeping a function call with a broken precondition. It is better to be explicit and force the programmer to correct his code.
[[unreachable]] on the other hand, is an assurance from the programmer that he know what he is doing. So unless the compiler can prove him wrong the compiler should accept.void foo(auto x) {if (x >= 0) {do_stuff();} else {[[unreachable]]; // branch can be optimized away since we say its ok.do_other_stuff();}}the same happen is we lift the precondition to the enclosing function.void foo(auto x) [expects:x>=0] {if (x >= 0) {do_stuff();} else {// branch can be optimized away as prcondition says its ok. Probably with a warning.do_other_stuff();}}Any UB from not fulfilling the precondition should only happen inside the called function.We would gain nothing from keeping a function call with a broken precondition. It is better to be explicit and force the programmer to correct his code./ Jonas
True, for hand-written code. But invalid or unreachable code paths can easily crop up in template code, macro expansions, platform-, library- or configuration-dependent code, generated code, and in edge cases such as loop or recursion terminating conditions. Why should we have to contort our code to remove the failing code when the compiler can easily do it for us?
On Friday, November 13, 2015 at 10:58:02 AM UTC, Giovanni Piero Deretta wrote:Oh, it would also be annoying that whether a program compile depends on the optimization level or the optimization passes enabled, although it is not necessarily a new thing.
On 2015-11-13 05:23, 'Edward Catmur' wrote:
> On Fri, Nov 13, 2015 at 12:45 AM, 'Edward Catmur' wrote:
>> Making precondition failure ill-formed excludes far too much otherwise
>> well-formed code:
>>
>> void print_column(string s, int width) {
>> for (int i = 0; i != width; ++i)
>> cout << ((i < s.size()) ? s[i] : ' '); // X
>> }
>> print_column("hello", 10);
>>
>> The compiler can prove that the program reaches line *X* with *i = 5*.
>> But s[5] is a precondition violation! Do you really want this code to be
>> ill-formed?
>
> OK, so let's write line *X* as:
>
> cout << ((*e*) ? s[i] : ' '); // X
>
> where *e* is an arbitrary (concrete) expression. In my code *e* is i <
> s.size(); in yours it's true. You're demanding the compiler be required to
> determine whether an arbitrary expression is extensionally equivalent to
> the precondition. But this is impossible; it's equivalent to the halting
> problem.
So... you've violated your own premise. You said that the compiler *can
prove* that s[i] with i==5 is executed. Now you're saying it can't.
If it can't prove it, then the code isn't ill-formed. If it *can* prove
it, then yes, IMO the code should be ill-formed, because the code *will*
cause UB when executed.
So, actually, in neither example can the compiler prove that a
precondition is violated. In the first case, the compiler ought to be
able to prove that it *isn't* violated, in the second case we just don't
know. So neither would be ill-formed.
On Fri, Nov 13, 2015 at 12:14 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
On Friday, November 13, 2015 at 10:58:02 AM UTC, Giovanni Piero Deretta wrote:
Oh, it would also be annoying that whether a program compile depends on the optimization level or the optimization passes enabled, although it is not necessarily a new thing.
I agree that would be annoying.
But it would be even more annoying if code I expected to run got randomly removed depending on optimization level.
I think if you expect this code to run you wouldn't mark it with unreachable() or [[unreachable]]. Again, for making the code have a defined behavior in case of a precondition violation you should spell out that behavior (e.g. by using terminate() or throw or whatever). At which point the precondition is removed and the failure reaction becomes part of the effects of your code. That is possible with the current language.
The __assume(false) intrinsic is an optimization hint and that is exactly what is missing in the language now. By using intrinsics like this I fully expect the program to crash and burn horribly if I turn out to be wrong when using it. If I don't want to take that responsibility then I don't use it.
The switch/case example that was presented earlier is not the only use of a tool like this. For instance, there is __builtin_assume_aligned in gcc which basically tells that a pointer has at least the specified alignment. I think, it should also work with __assume, but I never tried. As a result the compiler may be able to generate more efficient code working with that pointer. If the pointer is actually not as strongly aligned then the program will misbehave, and that is expected. Again, if you want this violation to be diagnosed in run time then you should spell that in the code. There are probably other examples as well.
I'm not familiar with the contracts proposal, but if it allows to implement the compiler hints like the above, then great, that's what we want. If not, that's fine as well - we just need a different tool for this.
That already happens, though it is far from "random". The compiler will happily remove code that depends on UB at higher optimization levels. Whether you expected it to run or not, or whether that annoys you, it does not care.