Unreachable attribute.

1,430 views
Skip to first unread message

Nicol Bolas

unread,
Nov 7, 2015, 8:55:26 AM11/7/15
to ISO C++ Standard - Future Proposals
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.

Daniel Krügler

unread,
Nov 7, 2015, 10:12:48 AM11/7/15
to std-pr...@isocpp.org
I'm not really sure that I have understood your request correctly,
could you please amend this by an example? At the moment, I'm
wondering why proper marking of never-returning functions with
[[noreturn]] wouldn't already provide the corresponding information to
the compiler.

Thanks,

- Daniel

Ville Voutilainen

unread,
Nov 7, 2015, 10:18:29 AM11/7/15
to ISO C++ Standard - Future Proposals
Because [[noreturn]] functions should never return, but functions that return
may contain unreachable paths. Having said that, I don't understand the
motivation to mark paths as unreachable, so I agree that better motivation
should be provided.

Nicol Bolas

unread,
Nov 7, 2015, 10:20:19 AM11/7/15
to ISO C++ Standard - Future Proposals

It's not "this function never returns". It's for cases like this:

int func(...)
{
  if(cond1)
  {
    ...
    return 5;
  }

  if(cond2)
  {
    ...
    return 10;
  }

  //unreachable
}
 
Even though `cond1` and `cond2` are two different expressions, which do not locally appear related, the overall system will ensure that at least one condition is true. If you put the [[unreachable]] attribute tag at `//unreachable`, then the compiler/static tool knows that this code should never be reached, and thus the lack of return is deliberate.

This could also be a switch statement over a range of values that you know will not be exceeded. Each individual condition returns a value, but at runtime the condition will never not be one of those values.

Ville Voutilainen

unread,
Nov 7, 2015, 10:23:54 AM11/7/15
to ISO C++ Standard - Future Proposals
On 7 November 2015 at 17:20, Nicol Bolas <jmck...@gmail.com> wrote:
> It's not "this function never returns". It's for cases like this:
>
> int func(...)
> {
> if(cond1)
> {
> ...
> return 5;
> }
>
> if(cond2)
> {
> ...
> return 10;
> }
>
> //unreachable
> }
>
> Even though `cond1` and `cond2` are two different expressions, which do not
> locally appear related, the overall system will ensure that at least one
> condition is true. If you put the [[unreachable]] attribute tag at
> `//unreachable`, then the compiler/static tool knows that this code should
> never be reached, and thus the lack of return is deliberate.
>
> This could also be a switch statement over a range of values that you know
> will not be exceeded. Each individual condition returns a value, but at
> runtime the condition will never not be one of those values.


So the purpose of this attribute is to quiesce diagnostics that tools otherwise
emit unless the code is convoluted to "handle" the missing cases (and such
"handling" may well be boilerplate nonsense). Having fought with such cases
occasionally, I'm beginning to like the idea.

Andrew Tomazos

unread,
Nov 7, 2015, 10:49:09 AM11/7/15
to std-pr...@isocpp.org
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.

The question is, what happens if you get it wrong, and an unreachable statement, contrary to your assertion, is executed.

I think the answer should be that what happens is the same or similar to an assert(false).  It is a constraint violation.

You should also study the existing practice before settling on your design.  In particular do code searches for the term "unreachable".  This is what such a facility is usually called.

In particular have a look at the LLVM unreachable instruction, and in clang there is an unreachable statement (I'm not sure right now if it compiles to the unreachable instruction, or the two are unrelated).

You should also search code.openhub.net and github for occurences of the term "unreachable" and see what they do.  You should enumerate the different existing facilities and discuss how they compare to your design.

Enjoy,
Andrew.



On Sat, Nov 7, 2015 at 2:55 PM, Nicol Bolas <jmck...@gmail.com> wrote:
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/.

Moritz Klammler

unread,
Nov 7, 2015, 10:52:30 AM11/7/15
to std-pr...@isocpp.org

> 2015-11-07 14:55 GMT+01:00 Nicol Bolas <jmck...@gmail.com>:
>
> 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.

This sounds a lot to me like GCC's `__builtin_unreachable`. From the
[documentation](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005funreachable-4123):

> If control flow reaches the point of the `__builtin_unreachable`, the
> program is undefined. It is useful in situations where the compiler
> cannot deduce the unreachability of the code.
>
> One such case is immediately following an asm statement that either
> never terminates, or one that transfers control elsewhere and never
> returns. In this example, without the `__builtin_unreachable`, GCC
> issues a warning that control reaches the end of a non-void function.
> It also generates code to return after the `asm`.
>
> int f (int c, int v)
> {
> if (c)
> {
> return v;
> }
> else
> {
> asm("jmp error_handler");
> __builtin_unreachable ();
> }
> }
>
> Because the `asm` statement unconditionally transfers control out of
> the function, control never reaches the end of the function body. The
> `__builtin_unreachable` is in fact unreachable and communicates this
> fact to the compiler.
>
> Another use for `__builtin_unreachable` is following a call a function
> that never returns but that is not declared
> `__attribute__((noreturn))`, as in this example:
>
> void function_that_never_returns (void);
>
> int g (int c)
> {
> if (c)
> {
> return 1;
> }
> else
> {
> function_that_never_returns ();
> __builtin_unreachable ();
> }
> }

Are these the semantics you have in mind? I think such a feature could
be useful if provided directly by the language rather than a compiler
specific extension.

Daniel Krügler

unread,
Nov 7, 2015, 10:53:35 AM11/7/15
to std-pr...@isocpp.org
So what precisely is the attribute referring to: Is this a "statement"
attribute and it would be used such as in

int func(...)
{
if(cond1)
{
...
return 5;
}

if(cond2)
{
...
return 10;
}

[[unreachable]]
}

? If so, this sounds to me like an odd use case for attributes.
Informally attributes apply to "source constructs such as types,
variables, names, blocks, or translation units" and I would like to
know of an existing compiler that would use an attribute for this. Can
you name an example? [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

Andrew Tomazos

unread,
Nov 7, 2015, 11:07:38 AM11/7/15
to std-pr...@isocpp.org
Formally an "attribute statement" is an attribute marked on a null statement.  There will be a precedent shortly with the fallthrough statement, a standardization of clang::fallthrough - but this won't/can't have semantic effect.

As I said in my previous message, I suspect the answer is that we want unreachable to do the same thing as an assert(false), as such I think it should be a library function like std::unreachable(), possibly marked [[noreturn]].  It is unclear if it should terminate or throw an exception or result in undefined behaviour.  It could only be an attribute if it had no runtime side-effect.

[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

Nicol Bolas

unread,
Nov 7, 2015, 11:20:39 AM11/7/15
to ISO C++ Standard - Future Proposals


On Saturday, November 7, 2015 at 10:49:09 AM UTC-5, Andrew Tomazos wrote:
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.

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.

Andrew Tomazos

unread,
Nov 7, 2015, 11:39:09 AM11/7/15
to std-pr...@isocpp.org
:)  You need a supermajority of committee votes to get this passed.  They do not give a crap what you want, they vote based on what they want.  It may be that an attribute statement with no semantic effect is the way to go, but you will need to show that you have carefully considered all the alternatives, have carefully studied existing practice, and have worked through all the different options in excruciating detail before settling on a design.  It's the only way to build the needed consensus.

Note that this is not the same thing as pre-deciding what you want and then writing a big long paper to try and justify it, people will see straight through that.

dgutson .

unread,
Nov 7, 2015, 12:17:22 PM11/7/15
to std-proposals

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?).

Bjorn Reese

unread,
Nov 7, 2015, 12:40:09 PM11/7/15
to std-pr...@isocpp.org
On 11/07/2015 04:53 PM, Daniel Krügler wrote:

> ? If so, this sounds to me like an odd use case for attributes.

Consider the following example:

enum values { one, two };

double convert(enum values v) noexcept
{
switch (v)
{
case one:
return 1.0;
case two:
return 2.0;
}
}

The switch statement does not contain a default case, because we want
a compiler warning if we add another enumerator and forget to handle it
in the switch statement.

Unfortunately, the above code also causes the compiler to warn that
there is no return after the switch statement. With an unreachable
attribute we can get the compiler warnings only when we want them.

Andrew Tomazos

unread,
Nov 7, 2015, 1:08:59 PM11/7/15
to std-pr...@isocpp.org
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.

We would like to get a warning that the switch needs to be updated with the new enumerator.  We need to be mindful of any interactions between unreachable statements and this case.

Also I see you marked this noexcept.  This would interact with whether or not unreachable can throw an exception, and needs consideration.

Thiago Macieira

unread,
Nov 7, 2015, 1:58:53 PM11/7/15
to std-pr...@isocpp.org
On Saturday 07 November 2015 17:07:34 Andrew Tomazos wrote:
> As I said in my previous message, I suspect the answer is that we want
> unreachable to do the same thing as an assert(false), as such I think it
> should be a library function like std::unreachable(), possibly marked
> [[noreturn]]. It is unclear if it should terminate or throw an exception
> or result in undefined behaviour. It could only be an attribute if it had
> no runtime side-effect.

We want the compiler to delete any code paths leading to that unreachable
marker. So there's no termination or exception because, as its name says, that
is not reached, ever.

Unless you meant as a debugging technique. The Q_UNREACHABLE() macro, in debug
mode, is a false assertion saying "unreachable reached" and terminates the
program.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Nov 7, 2015, 2:05:29 PM11/7/15
to std-pr...@isocpp.org
On Saturday 07 November 2015 08:20:39 Nicol Bolas wrote:
> > 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.
>
> 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.

Actually, it is the same thing.

Since falling off the edge of a function is undefined, if you really meant not
to return from there, it means reaching the closing brace is impossible.
Therefore, the "I'm not returning from here" means "this is unreachable".

Casey Carter

unread,
Nov 7, 2015, 3:23:10 PM11/7/15
to ISO C++ Standard - Future Proposals
On Saturday, November 7, 2015 at 12:08:59 PM UTC-6, Andrew Tomazos wrote:

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.

Ironically enough, we can write this function today: [[noreturn]] inline void unreachable() {}
Of course, now the problem is to keep the compiler from warning about the fact that unreachable returns despite being declared [[noreturn]].

Thiago Macieira

unread,
Nov 7, 2015, 4:33:32 PM11/7/15
to std-pr...@isocpp.org
On Saturday 07 November 2015 12:23:10 Casey Carter wrote:
> Ironically enough, we can write this function today: [[noreturn]] inline
> void unreachable() {}
> Of course, now the problem is to keep the compiler from warning about the
> fact that unreachable returns despite being declared [[noreturn]].

It either needs a compiler-specific token to indicate that the code is
unreachable or it needs to call another [[noreturn]] function.

Myriachan

unread,
Nov 9, 2015, 5:32:49 PM11/9/15
to ISO C++ Standard - Future Proposals
On Saturday, November 7, 2015 at 9:17:22 AM UTC-8, dgutson wrote:

> 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".  That's pretty much what GCC/Clang's __builtin_unreachable() and Visual C++'s __assume(false) do.  Often, these compilers will place a trap opcode at these locations, such as int3 for the x86 case.  Or they will change the code flow based on the assumption that the statement never executes.  Either one is within the purview of "undefined behavior".

For compilers that don't implement [[unreachable]] and thus ignore it, executing the statement properly falls within "undefined behavior" as well.

I suppose that the Standard could put a non-normative Note that [[unreachable]] is intended to suppress implementation-defined diagnostics regarding code paths that lead to undefined behavior (such as falling off the end of a non-void function).

Melissa

Nevin Liber

unread,
Nov 9, 2015, 5:52:13 PM11/9/15
to std-pr...@isocpp.org
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...
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Andrew Tomazos

unread,
Nov 9, 2015, 6:01:24 PM11/9/15
to std-pr...@isocpp.org
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.)

Myriachan

unread,
Nov 9, 2015, 6:02:26 PM11/9/15
to ISO C++ Standard - Future Proposals
On Monday, November 9, 2015 at 2:52:13 PM UTC-8, Nevin ":-)" Liber 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...


So does the definition of the existing [[noreturn]] attribute:

If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined.

Melissa

Andrew Tomazos

unread,
Nov 9, 2015, 6:03:15 PM11/9/15
to std-pr...@isocpp.org
On Tue, Nov 10, 2015 at 12:01 AM, Andrew Tomazos <andrew...@gmail.com> wrote:
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.

The confusion arises because whether or not undefined behavior has a semantic effect is unspecified. :) lol
 

Andrey Semashev

unread,
Nov 9, 2015, 6:09:05 PM11/9/15
to std-pr...@isocpp.org
I remember using __assume(false) in a switch/case branch so that the compiler
knew that a certain case or cases never happen. 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().

Myriachan

unread,
Nov 9, 2015, 6:13:30 PM11/9/15
to ISO C++ Standard - Future Proposals
On Monday, November 9, 2015 at 3:01:24 PM UTC-8, Andrew Tomazos wrote:
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.


An attribute resulting in undefined behavior has the convenient property that on an implementation that doesn't understand the attribute and thus ignores it, the implementation is still compliant: "continue executing normally as if nothing happened" is within the definition of "undefined behavior".
 
(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]

The "cmp eax" and "jae call_error_function" aren't necessary with [[unreachable]].

Melissa

Andrew Tomazos

unread,
Nov 9, 2015, 6:17:46 PM11/9/15
to std-pr...@isocpp.org
A std::unreachable() standard library call can necessarily do everything that an attribute statement does and more.  So, no it is possible.

I think the right approach would be to first design exactly what we want an unreachable statement to do - and then once that is decided, we can look at whether it is a better fit as a library function or attribute.

It sounds like there are tradeoffs here between it being an optimization tool, a static analysis tool, and/or a runtime diagnostic tool.

Richard Smith

unread,
Nov 9, 2015, 7:32:28 PM11/9/15
to std-pr...@isocpp.org
On Mon, Nov 9, 2015 at 2: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...

Ignoring all occurrences of the [[unreachable]] attribute would be a correct implementation of it, so I think it still satisfies our weakly-established criteria for what an attribute can do. 

Andrey Semashev

unread,
Nov 9, 2015, 7:43:22 PM11/9/15
to std-pr...@isocpp.org
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
> > wrote:
> >
> > I remember using __assume(false) in a switch/case branch so that the
> > compiler
> > knew that a certain case or cases never happen. 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.

My concern is that to make that library call the compiler would have to
compile all cases, which nullifies the effect of optimization. I guess the
optimization would only be possible if std::unreachable() was an intrinsic
function that has no side effects (i.e. a no-op).

> I think the right approach would be to first design exactly what we want an
> unreachable statement to do - and then once that is decided, we can look at
> whether it is a better fit as a library function or attribute.
>
> It sounds like there are tradeoffs here between it being an optimization
> tool, a static analysis tool, and/or a runtime diagnostic tool.

I think as a runtime tool it has little use - we can already use
std::terminate() to catch unexpected cases now.

Andrew Tomazos

unread,
Nov 9, 2015, 8:12:58 PM11/9/15
to std-pr...@isocpp.org
The use is in the case you get it wrong, and an unreachable statement, is reached.  The question is, what happens then?

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.
- Configurably one of the above.
- Something else?

Thiago Macieira

unread,
Nov 9, 2015, 8:27:30 PM11/9/15
to std-pr...@isocpp.org
On Tuesday 10 November 2015 02:09:02 Andrey Semashev wrote:
> I remember using __assume(false) in a switch/case branch so that the
> compiler knew that a certain case or cases never happen. 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().

Why not?

Thiago Macieira

unread,
Nov 9, 2015, 8:28:50 PM11/9/15
to std-pr...@isocpp.org
On Tuesday 10 November 2015 02:12:56 Andrew Tomazos wrote:
> 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.
> - Configurably one of the above.
> - Something else?

My preference: undefined behaviour. For two reasons:

1) it matches what happens if a [[noreturn]] function does return (undefined
behaviour)

2) it allows the implementation to do any of the other options if it wants to,
like in UBSAN mode.

Andrey Semashev

unread,
Nov 10, 2015, 3:08:30 AM11/10/15
to std-pr...@isocpp.org
On 2015-11-10 04:27, Thiago Macieira wrote:
> On Tuesday 10 November 2015 02:09:02 Andrey Semashev wrote:
>> I remember using __assume(false) in a switch/case branch so that the
>> compiler knew that a certain case or cases never happen. 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().
>
> Why not?

Because if that function is allowed to have side effects the compiler
will have to compile the code leading to its call.


dgutson .

unread,
Nov 10, 2015, 6:51:17 AM11/10/15
to std-proposals

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?
>

Tony V E

unread,
Nov 10, 2015, 4:01:03 PM11/10/15
to Standard Proposals
On Tue, Nov 10, 2015 at 6:51 AM, dgutson . <daniel...@gmail.com> wrote:

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?
>


Just to be clear about what some others are trying to point out:


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();
}

If std::unreachable() is defined to
- throw an exception
- std::terminate
- or be configurable

Then the above code can't be optimized.  In particular, I can write "correct" code that purposely passes in a bad 'day' so that an exception is thrown and then catch that exception and continue. If we _define_ it to throw an exception (or call my handler, etc), then it better throw an exception (or call my handler, etc).

However, if we leave it as undefined behaviour, then the code can be removed/optimized.  And optionally an implementation can offer various behaviours.

Tony

Andrew Tomazos

unread,
Nov 10, 2015, 4:24:45 PM11/10/15
to std-pr...@isocpp.org
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.

Nicol Bolas

unread,
Nov 10, 2015, 4:45:47 PM11/10/15
to ISO C++ Standard - Future Proposals

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.

Andrew Tomazos

unread,
Nov 10, 2015, 5:12:33 PM11/10/15
to std-pr...@isocpp.org
For example, NDEBUG + assert.h configures at compile-time the behavior of assert.



Matt Calabrese

unread,
Nov 10, 2015, 5:13:11 PM11/10/15
to ISO C++ Standard - Future Proposals
I would have agreed with you prior to Kona, but contracts are basically going down the configurable route. I don't think anybody talked about exact wording of how it might be specified in the standard, but the general idea I gleaned is that things like the ability to provide a run-time specification of a precondition violation handler would be configurable at build time. Violations could be UB, but you would be able to build in a way that lets you register a handler, making behavior upon violation defined, which then has the trade-offs mentioned in this thread. Though wording wasn't talked about in detail at Kona, I think the idea of how to specify this in the standard, without explicitly talking about things like build options, is that there would be some standard registration function that lets you specify a hook at run-time, however it would be implementation-specified whether or not invoking this function actually registers the hook (it might do nothing at all on some implementations, or it might quick_exit if you try to call it, etc.). This would mean in practice, without being too explicit, that the ability to register a handler at run-time can be build-time configurable. You have UB if the implementation specifies that the registration function doesn't actually register a handler.

Again, the details of this weren't talked about in detail regarding contracts a Kona even though there seemed to be agreement that there would be some kind of configuration available. This is just my personal interpretation of how it might actually be specified in a sensible manner in the standard. If this is really the way we'd be moving forward with contracts, it might be equally acceptable for a facility such as unreachable. In my personal opinion, I don't think unreachable needs such a hook, but it might not be outside of the realm of possibility that there is some kind of a configurable approach that is at least partially endorsed in the standard.

Richard Smith

unread,
Nov 10, 2015, 5:23:03 PM11/10/15
to std-pr...@isocpp.org
The purpose of SG12 is to rationalize C++'s handling of undefined behavior, not to get rid of it. 

Andrew Tomazos

unread,
Nov 10, 2015, 5:25:13 PM11/10/15
to std-pr...@isocpp.org
My bad.

Edward Catmur

unread,
Nov 10, 2015, 6:57:07 PM11/10/15
to ISO C++ Standard - Future Proposals
Note that if we get contracts and don't get [[unreachable]], one will be able to write []()[[expects: false]]{}() with presumptively similar results. I would hope this counts as an argument in favour of [[unreachable]] with similar UB semantics; code deletion in release mode, trap/throw/hook in debug mode.

Giovanni Piero Deretta

unread,
Nov 11, 2015, 7:04:19 AM11/11/15
to ISO C++ Standard - Future Proposals
On Tuesday, November 10, 2015 at 10:13:11 PM UTC, Matt Calabrese wrote:
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 [...].

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.

I see now that Edward Catmur made a similar comment elsethread.

-- gpd

-- gpd

David Krauss

unread,
Nov 11, 2015, 8:51:22 AM11/11/15
to std-pr...@isocpp.org

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]].

[[noreturn]] void foo();

switch ( sel ) {
case blah:
    foo();
    [[unreachable]]; // break and [[fallthrough]] are inappropriate,
                     // but it's nice to write something here.

default:
    [[unreachable]]; // Completing a switch is a common use-case
                     // for unreachable.
}

Giovanni Piero Deretta

unread,
Nov 11, 2015, 10:09:01 AM11/11/15
to ISO C++ Standard - Future Proposals


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?

-- gpd

Jonas Persson

unread,
Nov 11, 2015, 10:36:44 AM11/11/15
to std-pr...@isocpp.org
This 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.

  / Jonas 

Nicol Bolas

unread,
Nov 11, 2015, 11:23:56 AM11/11/15
to ISO C++ Standard - Future Proposals

What kind of [[unreachable]] are you talking about? My original idea was simply an attribute statement that tells the compiler that control flow within a function cannot reach this point. How would the caller of that function know about that and therefore be optimized by it?

You don't label a function as "unreachable". You label a place in the code.

Giovanni Piero Deretta

unread,
Nov 11, 2015, 11:28:23 AM11/11/15
to ISO C++ Standard - Future Proposals

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

Thiago Macieira

unread,
Nov 11, 2015, 12:04:00 PM11/11/15
to std-pr...@isocpp.org
On Wednesday 11 November 2015 16:36:42 Jonas Persson wrote:
> 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.

That's exactly what MSVC's __assume builtin does.

__assume(expr) assumes that expr is true, which means it is allowed to
optimise away any code paths that would lead to expr being false. If expr is
always false, the same stands true: all code paths leading there can be
optimised away.

Jonas Persson

unread,
Nov 11, 2015, 5:26:30 PM11/11/15
to std-pr...@isocpp.org
On Wed, Nov 11, 2015 at 5:28 PM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
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?

-- gpd

This 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.
  
It is a bit strange to reason about precondition UB that way. There is usefulness in assuming that the preconditions true and optimize for that, where it beeing false leads to UB. But here the compiler first proves the program to be incorrect and than uses that information for optimizations. Why optimize an incorrect program?

 
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.

  If the handler I choose is the compile time check(I dont know if this part of the proposal, but is really should be) any use of std::unreachable would be a compiler error.
 

Also the compiler (or static analyser) should complain only if it can statically prove that a function with invalid preconditions is actually called.

-- gpd

  Why would you want that? It would lead to runtime check for a lot conditions that are fully known at compile time. 

  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?

  / Jonas


Jonas Persson

unread,
Nov 11, 2015, 5:35:04 PM11/11/15
to std-pr...@isocpp.org
I fully agree  on that definition of [[unreachable]]. I was talking about the caller of std::unreachable() (or any function with failed a precondition)  in contrast to a built in attribute.

  / Jonas

Tony V E

unread,
Nov 11, 2015, 5:37:42 PM11/11/15
to Standard Proposals
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".

Tony


  / Jonas


Jonas Persson

unread,
Nov 11, 2015, 5:44:08 PM11/11/15
to std-pr...@isocpp.org
I 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) 

  / Jonas

Tony V E

unread,
Nov 11, 2015, 6:02:56 PM11/11/15
to Standard Proposals
 
  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".

Tony

I 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?)


Message has been deleted

Jonas Persson

unread,
Nov 12, 2015, 4:49:11 AM11/12/15
to std-pr...@isocpp.org
On Thu, Nov 12, 2015 at 12:02 AM, Tony V E <tvan...@gmail.com> wrote:
 
  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".

Tony

I 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?)


No, I dont think it should. It should fail. we dont know what x is. A precondition is there to force the caller check his data before calling, and in this case he hasn't. A correctness thing.
void foo(auto x) {
    if (x >= 0) {
       do_stuff();
    } else {
       x = sqrt(x);  // Call sqrt. Or fail to compile as x is statically known to be incorrect here.
       do_other_stuff();
    }
}
[[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

Giovanni Piero Deretta

unread,
Nov 12, 2015, 7:10:13 AM11/12/15
to ISO C++ Standard - Future Proposals


On Thursday, November 12, 2015 at 9:49:11 AM UTC, Jonas Persson wrote:
[...]

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.


the problem is that C++ doesn't really distinguish between different flavours of UB. C11 tried with Annex L, which I think that it is covered by the recent wave of 'sanitizer' support in compilers. It has a non-zero cost though as the additional checks are non-free and some optimizations are no longer possible.

Constraining UB inside a function is also a lost cause. I'm not a compiler writer, but as far as I understand, under aggressive inter-procedural optimizations and inlining most compilers really do not track function boundaries.

What you want is probably a 'santizer' version of preconditions: all violations are detected and will guarantee that the program will abort. That still allows optimizations as preconditions that have already been checked do not need to be checked over and over. I do not doubt that compilers will offer this option.

-- gpd

Jonas Persson

unread,
Nov 12, 2015, 8:16:03 AM11/12/15
to std-pr...@isocpp.org
I'm not a compiler writer either so I don't understand al details about UB, but even if UB is allowed to propagare everywhere and do whatever to the code,
in practice it is used for optimizations while keeping the bahavior of the program as close to expected as possible.

The problem of using a failed precondition to optimize away code is more practical. Suppose I have a function with a precondition that is already used in many places. I then make a slight modification to that precondition, and now suddenly the compiler is able to figure out at compile time that that precondition is false in some places. It then just remove all the branches leading to those calls without telling.
This is very brittle.

To reformulate. What I want is that a contract violation that the compiler figure out at compile time should be ill-formed and not undefined.

  / Jonas

Edward Catmur

unread,
Nov 12, 2015, 10:24:58 AM11/12/15
to std-pr...@isocpp.org
If that's what you want, then pass the appropriate compiler flags; in this case -fsanitize=contract-conditions. Meanwhile, those of us who value speed over safety will omit those flags and instead pass -fassume-contract-conditions.

Please understand: we're perfectly happy to allow a contract violation to result in a compile-time or runtime error; indeed we want that behavior for our debug builds. However, in order to permit the optimizer free rein in release builds, we need the standard to say that contract violation is UB. Since UB encompasses all possible behaviors including compile-time and run-time diagnostic termination, this permits your preferred behavior as well as code pruning.

Tony V E

unread,
Nov 12, 2015, 2:30:04 PM11/12/15
to Standard Proposals
OK, I see, and I agree with you - in part.  If the compiler can look at things and say "I can tell this leads to UB, which must never happen, so I can remove this code", you'd prefer that it says "I can tell this leads to UB, so I'm not going to compile it".

I think that is good for the obvious cases.  But I suspect there are cases, such as inlining and/or templates, where the code isn't "wrong", but when a few functions and templates are put together, the compiler determines a certain path shouldn't happen.  And maybe, due to higher up program logic it doesn't happen.  Not sure I can make a decent example:

void foo(auto x, Func f) {
    // for non-negative, do the other stuff first
    // otherwise it is important to do f() first (because blah...
    if (x >= 0) {
       do_stuff();
       x = f(x);
    } else {
       x = f(x);
       do_other_stuff();
    }
}

void handleMsg(Msg m) {
     switch (m.event) {
     case Blah:
         foo(m.data, sqrt);
         break;
     case Etc:
         ...
     }
}

Here the developer knows that a Msg with event == Blah always carries non-negative data.  (Maybe Msg is sanitized before calling handleMsg or before sending, etc).  So the developer knows that calling foo(m.data, sqrt) will never send negative data and never fall into foo()'s else case, and never hit undefined behaviour.  So passing sqrt for the function is not UB.

However, the compiler (without whole-program optimization) can't tell whether m.data might be negative.  ie can't tell in foo(x, f) whether x will be negative.
Should the compiler fail to compile foo()?  Or maybe fail when compiling handleMsg()?  Or should it look at foo(m.data, sqrt), including sqrt's x >= 0 precondition, and determine (when inlining into handleMsg) that the else case can't happen (because it would be UB, not because m.data is known to be non-negative), and can be removed?

I agree that the compiler should fail to compile when it sees UB that it _knows_ the UB _will_ be reached.  But when it sees UB that it doesn't know whether it will be reached or not (typically in a particular inlining situation), the compiler can _assume_ it won't be reached and remove it.
 
Tony

P.S. in the above example, if handleMsg's preconditions were detailed enough, maybe the compiler could _know_ that m.data is non-negative, and then it could remove the code when inlining foo() by _forward_ propagating the assumptions, instead of _back_ propagating via bumping into UB.  But until all preconditions are fully detailed, sometimes the compiler can only work backwards.

Maybe the compiler should issue an error/warning like "handleMsg() requires a precondition of { m.data >= 0 when m.event == Blah }"?  I don't think we are quite there yet, but current compilers do know how how far they back-propagate a condition, so I suppose they could issue a warning near the right place.  I just suspect that it would currently be a bunch of noise with no good ways to modify the code to stop the warnings.

P.P.S. the other case I've seen is using if-statements in templates, when you know it is a compile-time decision.  Like if (sizeof(T) < 16) {...}.  That can be done via enable_if or other tricks, but sometimes a "runtime" if is simplest - particularly when you know that the compiler is just going to remove the code completely under the right conditions.


[[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

Jonas Persson

unread,
Nov 12, 2015, 5:34:21 PM11/12/15
to std-pr...@isocpp.org
I dont think it is necessary to fall back to precondition UB here either. It is better to tell the compiler what the value of x is, so it can eliminate the else branch. Preconditions on handleMsg may work as you say, but might be tricky to write. An easy way of just stating our assumptions in the code would be a simpler first step.

void handleMsg(Msg m) {
  switch (m.event) {
    case Blah:
      assume(m.data>=0); // Give compiler info to eliminate branch. Explicit point of UB if not true
      foo(m.data, sqrt);
      break;
    case Etc:
      ...
  }
}
assume(expr) here is like MS version. It tells the compiler 'expr' is true. The compiler accepts unless it can prove otherwise
What we gain here is an explicit easy to spot location in the code where undefined behavior is created. Allowing us to reason about the code and possible bugs, and a place to start refactor into more correct code with preconditions on handleMsg or postcondition on m.data()
If that is not enough to help the compiler it may be better to split the function into foo and foo_non_neg.

I can see that this might become arbitrarily complex. But so will the task of understanding why the code didnt work. So I think that it will always be better to refactor into more explicit code, and there are no reason why that would lead to slower code.
 
  / Jonas

Jonas Persson

unread,
Nov 12, 2015, 5:42:54 PM11/12/15
to std-pr...@isocpp.org
In this case UB will not give you any better performance. If you get UB on the caller side of a precondition, it is becase the code is incorrect. If that causes the compiler to fail, you can remove the failing code, giving you the same as UB code pruning would. But with fewer subtle bugs. 

  / Jonas

Edward Catmur

unread,
Nov 12, 2015, 5:52:44 PM11/12/15
to std-pr...@isocpp.org

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?

Jonas Persson

unread,
Nov 12, 2015, 6:20:23 PM11/12/15
to std-pr...@isocpp.org
Note that I'm not talking about disallowing UB in general here, only for precondition callers. 
The reason to make the ill-formed is that 
1) the primary reason to use contracts is to catch misuse. Here we are detecting misuse, but we are not telling. 
2) it is not evident from reading the code that it will fail or be pruned. Only the compiler knows. 
3) We cannot be sure to catch this error by rebuilding in debug mode or with sanitisers, becase any change to the compile process might cause changes in inlining or other things so that the
compiler fails to figure out the failed precondition at compile time.
 
  / Jonas

Edward Catmur

unread,
Nov 12, 2015, 6:45:31 PM11/12/15
to std-pr...@isocpp.org
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?

Jonas Persson

unread,
Nov 13, 2015, 2:24:55 AM11/13/15
to std-pr...@isocpp.org
How is that precondition violation?  If operator[] has a precodition it should be i < size(), which is what you are checking.

On the other hand:
void print_column(string s, int width) {
    for (int i = 0; i != width; ++i)
        cout << s[i];  // X
}
print_column("hello", 10);

would be a violation and I would rather have the compile fail than an access violation or no output at runtime.
Properly fixing that with

void print_column(string s, int width) [expects:width<s.size()] {
    for (int i = 0; i != width; ++i)
        cout << s[i];  // X
}
print_column("hello", 10);

would let the compiler optimize the print_column function and tell you, hopefully at compile time, that your call to print_column is wrong. 

  / Jonas

Edward Catmur

unread,
Nov 13, 2015, 5:24:01 AM11/13/15
to std-pr...@isocpp.org
 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.

Jonas Persson

unread,
Nov 13, 2015, 5:56:21 AM11/13/15
to std-pr...@isocpp.org
No, I'm not saying it has to figure out anything. I am saying that iff it do find out, at compile time, that a function will be called with a broken precondition, it must fail and not cause UB.
If it don't figure out at compile time, and s[i] is actually called with out of range i , it is well-formed and can be UB.

  / Jonas

Giovanni Piero Deretta

unread,
Nov 13, 2015, 5:58:02 AM11/13/15
to ISO C++ Standard - Future Proposals

technically he is not requiring that fort the compiler to catch every possible precondition violation at compile time; he is only asking that *if* the compiler can determine it then it should be a compile error. The problem is that there is no realistic way to specify this behaviour in the standard; this is the job for a static analyzer, not the compiler.
 

Giovanni Piero Deretta

unread,
Nov 13, 2015, 6:14:57 AM11/13/15
to ISO C++ Standard - Future Proposals

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.
 

Edward Catmur

unread,
Nov 13, 2015, 6:20:50 AM11/13/15
to std-pr...@isocpp.org
For any non-trivial program there is always some input that will cause that function not to be called and so prevent that precondition being violated. (For example, invoking the program with --help command line option). Even a program where main violates a precondition may still be valid if linked against a translation unit containing a variable with static storage duration and namespace scope whose constructor terminates the program before main is entered:

int main() { vector<int>()[5]; }
int i = (exit(1), 42);

So there would be absolutely no point in a compiler implementing your requirement, as in practice the antecedent does not obtain.

Jonas Persson

unread,
Nov 13, 2015, 9:47:35 AM11/13/15
to std-pr...@isocpp.org
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.

Actually the compile error is not the central point. That could be a QoI issue. The main problem was the suggestion earlier  in this thread where an empty funtion with a false precondition

void unreachable() [[expects:false]] {}

would have the same semantic as [[unreachable]] and be able to prune branches.

if a call to this unreachable() do anything other that abort in some way or be a noop, it would be a big problem.
Basically making large reliable programs with contracts an impossible task to write.


  / Jonas

Jonas Persson

unread,
Nov 13, 2015, 10:19:16 AM11/13/15
to std-pr...@isocpp.org
The same could be said for most ill-formed code. And still some compilers find it useful to issue diagnostics.
The earlier you get notified of incorrect code the better IMHO.
 
  / Jonas

Edward Catmur

unread,
Nov 13, 2015, 10:37:18 AM11/13/15
to std-pr...@isocpp.org
Why is it a problem to you how other people choose to compile their programs?

Matthew Woehlke

unread,
Nov 13, 2015, 10:40:30 AM11/13/15
to std-pr...@isocpp.org
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.

--
Matthew

Edward Catmur

unread,
Nov 13, 2015, 10:41:33 AM11/13/15
to std-pr...@isocpp.org
Sure, it's a QOI issue. Compilers can't issue diagnostics on all invalid code, as much of it is not executed for reasons that are obvious to the programmer but not to the compiler.

Andrey Semashev

unread,
Nov 13, 2015, 10:47:42 AM11/13/15
to std-pr...@isocpp.org
On 2015-11-13 17:47, Jonas Persson wrote:
>
> On Fri, Nov 13, 2015 at 12:14 PM, Giovanni Piero Deretta
> <gpde...@gmail.com <mailto: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.

Edward Catmur

unread,
Nov 13, 2015, 11:01:59 AM11/13/15
to std-pr...@isocpp.org
On Fri, Nov 13, 2015 at 3:40 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
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.

It can prove that an expression containing s[i] with i==5 is evaluated; it may not be able to prove whether or not the subexpression s[i] is evaluated. These are different things.

If it helps, consider replacing the loop with a hand-unrolled equivalent.
 
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.

But who's to say that the code invoking UB *will* be 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.

Which is precisely my point; sure, issue a warning-level diagnostic if you're reasonably certain that a contract precondition will be violated, but a compiler has to accept the program (at least in its default configuration, without -Werror etc.), because only the programmer knows for sure whether it will actually be violated in the intended execution of the program.

Miro Knejp

unread,
Nov 13, 2015, 8:38:03 PM11/13/15
to std-pr...@isocpp.org
Am 13.11.2015 um 15:47 schrieb Jonas Persson:
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.
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.

Jonas Persson

unread,
Nov 16, 2015, 10:45:24 AM11/16/15
to std-pr...@isocpp.org
On Fri, Nov 13, 2015 at 4:47 PM, Andrey Semashev <andrey....@gmail.com> wrote:

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 point was that didnt mark it unreachable, I violated a precondition. And I don't want a runtime effect if I can catch the error at compile time.
 

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.


 I'm not really familliar with the details of the proposal either, it is supposed to appear in the post kona mailing IIUC. But I am familliar with contracts in programming. 

The main purpose of contracts is to enforce correct usage. The performace improvements is just a lucky side effect.
Contracts are also  by definition something that are known at design time, and ideally should be statically enforced. That would give the best safety and runtime performace. But it probably is a bit tricky to specify in the standard, and there is a cost in programmer effort and compile times.
But hopefully it can be introduced somewhere in the future.

Considering this, it would be really unfortunate to assign sematics that prevent compile time checking.
It is far better to provide some more specialized tools, like assume().

Assume can then be used to avoid compile errors from the precondition:

void foo() [[expects: x>0]] {}

void bar() {
  ...
  assume(x>0);
  foo();
}

assume introduces the UB in a explicit way that is easy to spot and understand when reading the code.

  / Jonas

Jonas Persson

unread,
Nov 16, 2015, 10:46:59 AM11/16/15
to std-pr...@isocpp.org
On Sat, Nov 14, 2015 at 2:38 AM, Miro Knejp <miro....@gmail.com> wrote:
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.

The issue was not if UB is allowed to remove code or not. I have no problem with that. The issue was that a precondition validate should be ill-formed rather that UB.

  / Jonas
 

Arthur O'Dwyer

unread,
Nov 16, 2015, 7:53:38 PM11/16/15
to ISO C++ Standard - Future Proposals
A contract violation can't be ill-formed; that's a term of art that specifically refers to something compile-time diagnosable (except in cases that are "ill-formed, no diagnostic required", about which the less said the better). Contracts are by definition checkable only at runtime. If you want to check something at compile-time, that's what the type system is for.
For a concrete example:

void compiletime(nn<int*> ptr) { std::cout << *ptr; }
void runtime(int *ptr) [[expected: ptr != nullptr]] { std::cout << *ptr; }

In the former case, we can document within the type system that "ptr" must never be null, and the compiler can help check that invariant for us. This is already a solved problem.

In the latter case, we're explicitly creating a function signature that (at compile time) accepts a wide range of possible inputs — and then we're using the contract to narrow down the "acceptable" range of inputs at runtime. So for example in the former case

    int i, *p = &i;
    compiletime(p);  // simply will not compile, because there is no implicit conversion from int* to non_null<int*>
    nn<int*> p2 = NN_CHECK_ASSERT(p);
    compiletime(p2);  // will compile fine

    int *p = nullptr;
    runtime(p);  // will compile, but violates the contract at runtime

My understanding of the Kona "contracts" discussion is that when you violate a contract, you get behavior that is defined (only) by the contract handler that you have chosen to use in the current (program/thread/context). If you've defined your contract-violation handler to call std::terminate(), then a contract violation causes perfectly well-defined program termination. If you've defined your contract-violation handler to call __builtin_unreachable() or divide by zero or whatever, then a contract violation causes undefined behavior.

If the implementation can detect at compile time that your contract-violation handler unconditionally invokes undefined behavior, then naturally the implementation is free to optimize based on that knowledge — i.e., the knowledge that contracts are never violated by your program. (Because if you ever violated any contract, you'd have UB, and valid C++ programs aren't allowed to have UB, so Q.E.D. your program can't violate any contract.) However, this kind of optimization is possible only if the implementation can detect that you want contract violations to have UB. It's not something that the implementation can just do for the hell of it.

IMHO the hard part of specifying contracts (besides the syntax bikeshed) is figuring out a way for the user to specify unambiguously and portably that he wants contract violations to have UB. Otherwise, you end up with stupid duplications of the form

void align(int y, int x) [[expected: ispowerof2(x)]]
{
    assume(ispowerof2(x));  // now let the compiler know that it should actually be optimizing here
    return y / x * x;
}

It's super easy to implement the "assume-macro" form of contracts today. You just write this:

#ifdef OPTIMIZE_HEAVILY
 #define assume(x) (!(x) && __builtin_unreachable())
#elsif DEBUG_BUILD
 #define assume(x) assert(x)
#elsif SAFETY_FIRST_HA_HA
 #define assume(x) (!(x) && throw my::contract_violation_error("Violated contract " #x));
#else
 #define assume(x) (void)0
#endif

The hard parts IMHO are all involved with figuring out how to "de-macro-ize" this idiom, while still enabling decent implementations to "see through" whatever functions or builtins or whatever are being introduced and optimize the code just as well as if it had been written in the "assume-macro" idiom.

C++03's std::terminate has the problem that I think is hard to solve. I'd love to be able to install my own terminate handler that has UB, in a way that told the compiler unambiguously and portably "hey, you can safely assume that my program never reaches std::terminate". However, the std::set_terminate() mechanism really isn't conducive to that at all — it's super duper hard for the implementation to "see through". I'd hate to see something equally sucky (say, "std::set_contract_violation()") get standardized. However, there are enough smart people working on the problem that I don't think that's likely to happen. I don't know what is likely to happen, though.

my $.02,
–Arthur
Reply all
Reply to author
Forward
0 new messages