C++ Logo

std-discussion

Advanced search

Re: Is forward progress guarantee still useful?

From: David Brown <david.brown_at_[hidden]>
Date: Sat, 20 Sep 2025 18:43:45 +0200
On 20/09/2025 17:33, Thiago Macieira via Std-Discussion wrote:
> On Saturday, 20 September 2025 07:52:47 Pacific Daylight Time David Brown via
> Std-Discussion wrote:
>>> That will_exit() function comes from some third-party library and isn't
>>> marked [[noreturn]]. We know it won't return, so we don't want the
>>> compiler to generate unnecessary code. So the author of this code needs
>>> to insert a __builtin_unreachabble() to suppress the "control reaches end
>>> of non-void function" warning. But note how there's no difference in code
>>> generation: the __builtin simply suppresses the warning.
>>
>> There can be a difference in code generation - since the compiler knows
>> that a "__builtin_unreachable();" after the "will_exit();" call can't be
>> reached, it can simplify the generation of "f" - there is no need for a
>> function epilogue, and that may also mean a simplified prologue.
>
> It isn't about will_exit() being reached. It's about it returning: since it is
> UB if it did, the compilers can reason it won't.

I realise that - it is the "__builtin_unreachable()" call that the
programmer promises will never be reached. (Perhaps I was not clear.)

> And they do:
>
> https://gcc.godbolt.org/z/vodMbj5x6
>
> All four compilers (including the old Intel) generate the exact same code with
> and without the __builtin_unreachable(). Now, ICC and MSVC generated a tail-
> call optimisation, which would allow returning to work, but that happens to be
> the most optimal code emission anyway.

Without the __builtin_unreachable() (or a [[noreturn]] attribute, or
similar), the code has UB anyway - if "will_exit()" returned, then "f"
would fall off the end of a non-void function. So in this particular
case, the compiler could assume that "will_exit()" will never return,
regardless of any extra annotation. (But helpfully the compilers can
warn about the missing return - it's more likely to be a mistake than
anything else.)

In a bigger function, the use of __builtin_unreachable() like this can
lead to more efficient code. I have used it for that purpose - in small
embedded systems, it is normal that "main()", or a function that it
calls, never returns but enters an unending loop (but hopefully not a
trivially infinite or do-nothing loop - you want your system to do
something useful!). __builtin_unreachable() or [[noreturn]] attributes
can reduce code and save stack space.

>
>> That can be with
>> compiler flags (like sanitisers) or with "__builtin_trap();" (I see it
>> in gcc - I expect clang has it too), or other implementation-specific
>> features. (There will also be std::breakpoint() in C++26, which might
>> be appropriate. But I don't know what that is supposed to do if you are
>> not running with a debugger.)
>
> I expect they will emit the debugging trap instruction, which on x86 is INT 3.
> That happens to be what MSVC emits if you add the __assume(0) after the
> will_exit() above, actually. If you execute this instruction, it will raise
> the #BP (breakpoint) exception and if there's no debugger, that is delivered
> to the application as a crash. On Unix systems, it'll be the SIGTRAP signal.
>
> BTW, GCC and Clang emit the UD2 instruction __builtin_trap(), which results in
> SIGILL. The old ICC emitted INT 5, which is unexpected... Boundary Range
> exception?
>

That all sounds reasonable enough.

>> So I am fully in agreement that if you want a particular kind of defined
>> behaviour, don't write UB and expect the compiler to read your mind.
>> But I still think that sometimes it is possible for a compiler to make a
>> guess at the developer's mind!
>
> Indeed, and in those cases the compiler should also print a warning. It's the
> famous case of -Wstrict-overflow with GCC.
>

The more warnings the compiler can give, the better IMHO - I am always
happy to have my mistakes found early!

Received on 2025-09-20 16:43:51