74

I read this question about noreturn attribute, which is used for functions that don't return to the caller.

Then I have made a program in C.

#include <stdio.h>
#include <stdnoreturn.h>

noreturn void func()
{
        printf("noreturn func\n");
}

int main()
{
        func();
}

And generated assembly of the code using this:

.LC0:
        .string "func"
func:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $.LC0, %edi
        call    puts
        nop
        popq    %rbp
        ret   // ==> Here function return value.
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        call    func

Why does function func() return after providing noreturn attribute?

msc
  • 33,420
  • 29
  • 119
  • 214
  • 22
    Please forgive me for asking, but, how is this 'noreturn' feature remotely useful to anyone? What is it used/misused for? – Martin James Aug 31 '17 at 12:45
  • I mean, I often write function that never return, (eg. a 'while(true){};' loop in an app-lifetime thread), but why would the compiler need to know? – Martin James Aug 31 '17 at 12:47
  • Is this not like a 'no road wheels' option when ordering a new car? – Martin James Aug 31 '17 at 12:48
  • 16
    @MartinJames, your question looks like a dupe of [this](https://stackoverflow.com/questions/10538291/what-is-the-point-of-noreturn) :P – ForceBru Aug 31 '17 at 12:55
  • 17
    "I read this question about `noreturn` attribute in C" No. The question you linked is about "`noreturn` attribute in C++" which is a different language. – Gerhardh Aug 31 '17 at 13:01
  • 14
    @MartinJames noreturn allows the compiler to do some optimisations. Most important is that error handling that ends in `exit` or equivalent doesn't turn leaf functions into non-leaf functions. So you can have `assert` or similar in the deepest bowels of your performance critical code and it doesn't cost more than a correctly predicted branch (instead of the function having to set up a frame, save registers, avoiding caller saved registers, etc.). – Art Aug 31 '17 at 13:01
  • 1
    Overly simplistic example here: https://godbolt.org/g/n46nc2 `foo1` has "error handling" that can return, `foo2` doesn't. `foo1` uses ebx instead of eax to do the computation (because eax can be overwritten by `error1`) and since ebx is callee saved, it also needs to be dumped on the stack. (note: this has to be compiled at -O1 because at -O2 gcc figures out a more clever way to do it) – Art Aug 31 '17 at 13:19
  • @Art OK, thanks for the info. It's still weird, though;) – Martin James Aug 31 '17 at 13:20
  • 4
    @Gerhardh OP may have linked a C++ question. But this is clearly a C code and `noreturn` is *not* (only) a C++ thing. – P.P Aug 31 '17 at 15:22
  • "*I read this question ...*" but it seems you did not read its answers, right? – alk Aug 31 '17 at 16:41
  • 5
    *"Here function return value."* Actually, no; it doesn't. The x86 `ret` instruction just means that execution is transferred back to the caller. You'd see the same thing for a function with a return value of `void`. It returns *control*, in other words; not a value. Now, all x86 calling conventions return values in the `EAX` register, but the caller and callee have to agree on that, so if the function returns `void` or nothing, then `EAX` will just contain garbage. Again, as the answers have said, according to the C language standard, the behavior is undefined, but this is what the asm means. – Cody Gray - on strike Sep 01 '17 at 00:54
  • 1
    I don't know what to make of your remark, @user28434. You seem to have missed the point of my comment. In the code in the question, there is a comment on the assembly output that says "Here function return value". That remark is not correct; a `ret` instruction does not mean that the function returns a value. All it means is that a transfer of control is happening. None of this has anything to do with the `noreturn` annotation. That's a C language feature, not relevant to the object code being disassembled. If you try and return from a `noreturn` function, that's UB, as already established. – Cody Gray - on strike Sep 01 '17 at 08:55
  • 1
    it's just lying to the compiler. the compiler knows more about C than you, so don't lie to it that this function doesn't return. – cat Sep 01 '17 at 16:43
  • 4
    Notice, however, what happened to `main`: there are no instructions at all after the `call`. So when `func` returns, like you promised it wouldn't, your program counter will be at an invalid location! – aschepler Sep 01 '17 at 23:47
  • @MartinJames An example from Swift: Swift has a control flow statement called a `guard` statement. It works like an assert, either the assertion is meant, or the `else` block executes. Unlike an `if`/`else`, this `else` block MUST exist the scope. Exiting the scope can be done by `break`, `continue`, `return`, or `throw`. But what about calling `exit()`? That exits the scope, but how is the compiler to know that? Hence Swift has a type, called `Never`, which is the return type of functions that never return. Calling a `Never` function satisfy's the guard's requirement to exit scope. – Alexander Sep 03 '17 at 14:58
  • 2
    Another potential use for this would be embedded systems, where you don't want the caller to stack the return address etc. Relevant to some parts of the start-up code, but also to `void main (void)`, which in a freestanding application should never return. – Lundin Sep 04 '17 at 11:41
  • when I compiled the posted code, the compiler output a warning message: "warning: 'noreturn' function does return'" and the associated arrow is pointing at the closing brace of the `func()` function. – user3629249 Sep 05 '17 at 20:15
  • @MartinJames On Linux, programs do not return to the kernel in order to exit; they must perform an `exit` system call in order to end the process. The C runtime uses `noreturn` to implement this: the program's entry point is not actually `main`, it is a `noreturn` function that performs the system call after calling `main`, thereby ensuring the program will exit correctly without a segmentation fault. – Matheus Moreira Sep 06 '17 at 09:58

9 Answers9

123

The function specifiers in C are a hint to the compiler, the degree of acceptance is implementation defined.

First of all, _Noreturn function specifier (or, noreturn, using <stdnoreturn.h>) is a hint to the compiler about a theoretical promise made by the programmer that this function will never return. Based on this promise, compiler can make certain decisions, perform some optimizations for the code generation.

IIRC, if a function specified with noreturn function specifier eventually returns to its caller, either

  • by using and explicit return statement
  • by reaching end of function body

the behaviour is undefined. You MUST NOT return from the function.

To make it clear, using noreturn function specifier does not stop a function form returning to its caller. It is a promise made by the programmer to the compiler to allow it some more degree of freedom to generate optimized code.

Now, in case, you made a promise earlier and later, choose to violate this, the result is UB. Compilers are encouraged, but not required, to produce warnings when a _Noreturn function appears to be capable of returning to its caller.

According to chapter §6.7.4, C11, Paragraph 8

A function declared with a _Noreturn function specifier shall not return to its caller.

and, the paragraph 12, (Note the comments!!)

EXAMPLE 2
_Noreturn void f () {
abort(); // ok
}
_Noreturn void g (int i) { // causes undefined behavior if i <= 0
if (i > 0) abort();
}

For C++, the behaviour is quite similar. Quoting from chapter §7.6.4, C++14, paragraph 2 (emphasis mine)

If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined. [ Note: The function may terminate by throwing an exception. —end note ]

[ Note: Implementations are encouraged to issue a warning if a function marked [[noreturn]] might return. —end note ]

3 [ Example:

[[ noreturn ]] void f() {
throw "error"; // OK
}
[[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0
if (i > 0)
throw "positive";
}

—end example ]

JDługosz
  • 5,592
  • 3
  • 24
  • 45
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • 1
    @MartinJames: I feel the same about gcc `pure`/`const` attributes, which basically just state that the developer *promises* to make a function which will not do any side effects, or `nonnull` which is a promise that you won't pass a null pointer to the function so the compiler ([allowing it to remove any `null` checks you might do inside the function](http://l.longi.li/blog/2010/04/19/gcc-s-attribute-nonnull-not-helpful-at-all/)). – vgru Aug 31 '17 at 12:55
  • 21
    @MartinJames - This feature is of immense benefit to static analysis tools, especially ones that perform worst-case stack depth analysis. Without the `noreturn` attribute on a function that truly won't return, the results of such tools would be incorrect. It *can* also be helpful for optimizations (saving a few bytes at most, every time such a function is called) because the compiler does not have to account for the function returning; control flow within a calling function simply ceases at that point. – phonetagger Aug 31 '17 at 13:58
  • 37
    tl;dr: `noreturn` doesn't mean "this function won't return"; it means "tell the compiler it can assume this function won't return". It's not there to make _your_ job easier, it's there to make the _compiler's_ job easier. – Hong Ooi Aug 31 '17 at 16:42
  • 15
    @MartinJames: Think about it this way: figuring out whether a function will return or not is *literally* the halting problem. It is, however, also an interesting property, both for correctness and for optimization. So, what can we do when we have a property that we want to decide but is actually *the* stereotypical undecidable property? We have to ask the programmer. Note that languages with more expressive type systems than C usually also have types for this. E.g. Scala has type `Unit` for a function that returns nothing and type `Nothing` for a function that doesn't return. – Jörg W Mittag Aug 31 '17 at 17:42
  • @MartinJames Look into [bottom types](https://en.wikipedia.org/wiki/Bottom_type) for more information. – Theodoros Chatzigiannakis Aug 31 '17 at 17:58
  • "Should" and "encouraged" are not requirements. "Must" is a requirement. Compilers are not required to warn for this. – user2357112 Aug 31 '17 at 18:01
  • Why *does* func emit a `ret` instruction (instead of `ud2` or nothing)? – Random832 Aug 31 '17 at 18:01
  • @Random832 Because the compiler decided to be "nice": without `ret` the program would "loop" (actually recurse) infinitely and eventually exhaust stack space because each `call` allocates a new stack frame. See @Steve Summit's answer for what else the compiler could arrange to be done in such a case. – MauganRa Aug 31 '17 at 18:18
  • 4
    @MauganRa That was why I suggested `ud2`, IMO this (aborting the program, the same thing is done in certain other cases that can be statically proven to be undefined behavior) would be "nicer" than returning into a caller that's been optimized to assume it won't. – Random832 Aug 31 '17 at 18:50
  • @Random823 That would indeed be the most beneficial thing, along with a warning during compilation. – MauganRa Sep 01 '17 at 08:15
  • @Random832 Because – if I'm reading other comments correctly – the use of `noreturn` doesn't _make_ the function not return, it just tells the compiler that it _can assume_ that the function _will never return_. In this instance, the author of the code is clearly lying through their teeth! – TripeHound Sep 01 '17 at 08:32
  • Am I mistaken in assuming this would be helpful with tail-call optimization or continuation-passing style? If the function won't return, you don't need to save a return address when the function is called, and the stack frame could be reused by the last function called in the non-returning function. – chepner Sep 01 '17 at 12:20
  • @TripeHound I was suggesting that it assume that the function will never return while generating code for the function itself (as if `__builtin_unreachable` were called at the end). Though an illegal instruction trap would be helpful for catching the "impossible" case at runtime and not add much cost (only two extra bytes). – Random832 Sep 01 '17 at 16:17
  • I guess a compiler could do that (and perhaps would be helpful if it did) since in both C/C++ a returning `noreturn` is UB. I suspect a possible reason many/most/all don't (apart from the fact they're not mandated to) is that `noreturn` is a _hint_ to the compiler, but "_the code is the code_" and if the latter (albeit incorrectly) performs a return, that trumps what the hint says. – TripeHound Sep 01 '17 at 16:30
51

Why function func() return after providing noreturn attribute?

Because you wrote code that told it to.

If you don't want your function to return, call exit() or abort() or similar so it doesn't return.

What else would your function do other than return after it had called printf()?

The C Standard in 6.7.4 Function specifiers, paragraph 12 specifically includes an example of a noreturn function that can actually return - and labels the behavior as undefined:

EXAMPLE 2

_Noreturn void f () {
    abort(); // ok
}
_Noreturn void g (int i) {  // causes undefined behavior if i<=0
    if (i > 0) abort();
}

In short, noreturn is a restriction that you place on your code - it tells the compiler "MY code won't ever return". If you violate that restriction, that's all on you.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Though Sourav's answer provides a more complete answer from a technical standpoint, this answer captures the real issue more succinctly. In my opinion THIS is the best answer! "Because you wrote code that told it to." – phonetagger Aug 31 '17 at 14:01
  • @phonetagger no doubt, this is a very targeted answer and perfectly to the point. The bigger problem in the question is, with a piece of code involving UB, there is no guarantee that it'll build to produce a binary, at all. Treat all warnings as errors and you won't be having a return, at all. :) – Sourav Ghosh Aug 31 '17 at 15:38
  • @SouravGhosh – I disagree about no guarantee that it’ll produce a binary at all, unless (as you suggest) the compiler is set to treat all warnings as errors. "UB" is with respect to the compiled executable, not with respect to the compiler itself. But the question was simple: "Why does function `func()` return after providing `noreturn` attribute?" Your answer is relevant, but doesn't really answer the question per se. This does: "Because you wrote code that told it to." Yours answers a hypothetical different question: “Why does my `noreturn` function crash the program when it returns?” – phonetagger Aug 31 '17 at 16:40
  • 7
    @phonetagger It is perfectly legal for a program containing undefined behavior to send a signal back in time that kills the compiler before it can produce the program. (This may not be permitted by *physics*, but the standard has no problem with it.) – Ray Sep 01 '17 at 17:53
  • 1
    @Ray - Legal perhaps, but not realistic, even if such were permissible by physics. People on SO are wont to make up wild examples of what might result from UB, from [purple dragons](https://stackoverflow.com/a/10456164/1245420) or [little demons coming out of your nostrils](https://stackoverflow.com/a/13444785/1245420) to [exploding refrigerators and time warps](https://stackoverflow.com/a/37579879/1245420). If we’re realistic though, [we can predict most of the likely results of UB](https://stackoverflow.com/a/14636606/1245420), even though they may vary from one environment/build to another. – phonetagger Sep 01 '17 at 20:23
  • 7
    @phonetagger Actually, as time goes on, the number of times you can say "yeah, I know, strictly speaking it's undefined, but in practice nothing too bad will happen" keeps getting smaller and smaller. The smarter the compilers get, the more leeway they seem to grant themselves to do really crazy stuff if you give them any excuse. – Steve Summit Sep 01 '17 at 21:01
  • @SteveSummit - I have never casually sat back in my reclining office chair, pulled up my spittoon, and said "Yeah, I know," (spit) "strickly speakin', it's underfined, but in prackiss, nuttin' too back never happint when we do it dat way. Oh, sorry 'bout missin' the 'toon, here's a hanky for yer shoe." On the contrary, I am saying that outlandish examples of what might result from UB may be funny, but they do not help confused new programmers better understand the machine within which their programs run. Real UB always has reasonable explanations if you dive into the resultant assembly code. – phonetagger Sep 01 '17 at 21:24
  • 3
    @phonetagger: Here's a real example of surprising UB: https://godbolt.org/g/uZibdL recent clang++ compiles non-void functions that end without a `return` into infinite loops, apparently on purpose to help you catch the error if you missed the warning (or maybe cases where it didn't warn because it wasn't sure that path would ever be taken). It doesn't do this for C because [it's only UB in C99/C11 if the caller uses the result](https://stackoverflow.com/questions/20614282/why-does-this-c-snippet-compile-non-void-function-does-not-return-a-value#comment30891689_20622422). – Peter Cordes Sep 02 '17 at 01:02
  • @PeterCordes - I don't find that surprising UB; it seems like a good proactive diagnostic tool for people who haven't learned to use `-Werror` to treat warnings as errors (or can't due to legacy issues). But regardless of whether one might find that behavior surprising, you'll note that it's in the realm of reasonable, not the realm of fanciful. My complaint with typical SO examples of UB is that they almost invariably wander into fanciful and unrealistic examples. While those may be funny, they miss the fact that there is always a mundane explanation for the actual behavior observed. – phonetagger Sep 02 '17 at 15:56
  • 4
    @phonetagger We don't use the fanciful (and *memorable*) examples because we actually think that UB will cause time traveling purple dragons to chase the nasal demons into an exploding fridge. We do it to keep people from assuming that the UB *must* work a certain way because all the other options are ludicrous. *"Even ludicrous outcomes are possible and no assumptions are safe"* is the idea we want to get across. Some UB manifestations are so genuinely weird that nobody would expect them. (I didn't even write this comment; I just ran a program containing `i = i++;` and it *appeared*.) – Ray Sep 03 '17 at 02:17
  • @Ray - This back-and-forth between me and the four of you has gotten out-of-hand; I'm sorry it all appears under Andrew Henle's answer. Go back to my original comment. I simply said although Sourav's answer has a lot of good info, it didn’t (as he originally posted it, not as it appears now) technically answer the OP's question, unless you infer more from his original answer than he wrote. By contrast... – phonetagger Sep 05 '17 at 19:55
  • 2
    By contrast, Andrew answered the OP's question directly, in his opening sentence. The answer isn’t “Since it’s UB it could do *anything*; holy cow, as random chance would have it, it returned! What *luck*!” The answer is, it returned because the OP’s code says to do so. Of course any answer would be remiss without mentioning the UB, which Andrew also does. But as it stands now, Sourav’s edited answer is now the better answer. – phonetagger Sep 05 '17 at 19:56
28

noreturn is a promise. You're telling the compiler, "It may or may not be obvious, but I know, based on the way I wrote the code, that this function will never return." That way, the compiler can avoid setting up the mechanisms that would allow the function to return properly. Leaving out those mechanisms might allow the compiler to generate more efficient code.

How can a function not return? One example would be if it called exit() instead.

But if you promise the compiler that your function won't return, and the compiler doesn't arrange for it to be possible for the function to return properly, and then you go and write a function that does return, what's the compiler supposed to do? It basically has three possibilities:

  1. Be "nice" to you and figure out a way to have the function return properly anyway.
  2. Emit code that, when the function improperly returns, it crashes or behaves in arbitrarily unpredictable ways.
  3. Give you a warning or error message pointing out that you broke your promise.

The compiler might do 1, 2, 3, or some combination.

If this sounds like undefined behavior, that's because it is.

The bottom line, in programming as in real life, is: Don't make promises you can't keep. Someone else might have made decisions based on your promise, and bad things can happen if you then break your promise.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
16

The noreturn attribute is a promise that you make to the compiler about your function.

If you do return from such a function, behavior is undefined, but this doesn't mean a sane compiler will allow you to mess the state of the application completely by removing the ret statement, especially since the compiler will often even be able to deduce that a return is indeed possible.

However, if you write this:

noreturn void func(void)
{
    printf("func\n");
}

int main(void)
{
    func();
    some_other_func();
}

then it's perfectly reasonable for the compiler to remove the some_other_func completely, it if feels like it.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • About messing with the state of the application, at some point I made a program where the main function had no return statement, and the _only_ way to close it was by logging out (it wouldn't close it self, I couldn't X it out (there was a blank graphics window for some reason), I couldn't force quit it via Task Manager nor terminal...). BTW, this was on Windows, not sure what compiler. – Solomon Ucko Sep 01 '17 at 02:33
11

As others have mentioned, this is classic undefined behavior. You promised func wouldn't return, but you made it return anyway. You get to pick up the pieces when that breaks.

Although the compiler compiles func in the usual manner (despite your noreturn), the noreturn affects calling functions.

You can see this in the assembly listing: the compiler has assumed, in main, that func won't return. Therefore, it literally deleted all of the code after the call func (see for yourself at https://godbolt.org/g/8hW6ZR). The assembly listing isn't truncated, it literally just ends after the call func because the compiler assumes any code after that would be unreachable. So, when func actually does return, main is going to start executing whatever crap follows the main function - be it padding, immediate constants, or a sea of 00 bytes. Again - very much undefined behavior.

This is transitive - a function that calls a noreturn function in all possible code paths can, itself, be assumed to be noreturn.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • Huh, that's weird. `noreturn` seems to disable tailcall optimization (as well as stopping inlining if the noreturn function calls printf). You can see this even with `-O3`: https://godbolt.org/g/vFWp8E. Clang 4.0 and gcc 7.2 are the same here. – Peter Cordes Sep 02 '17 at 01:15
  • Ah, it's because gcc's `noreturn` handling specifically disables tailcall optimization for better `abort()` backtraces: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55747#c4. – Peter Cordes Sep 02 '17 at 03:20
8

According to this

If the function declared _Noreturn returns, the behavior is undefined. A compiler diagnostic is recommended if this can be detected.

It is the programmer's responsibility to make sure that this function never returns, e.g. exit(1) at the end of the function.

ChrisB
  • 498
  • 3
  • 10
6

ret simply means that the function returns control back to the caller. So, main does call func, the CPU executes the function, and then, with ret, the CPU continues execution of main.

Edit

So, it turns out, noreturn does not make the function not return at all, it's just a specifier that tells the compiler that the code of this function is written in such a way that the function won't return. So, what you should do here is to make sure that this function actually doesn't return control back to the callee. For example, you could call exit inside it.

Also, given what I've read about this specifier it seems that in order to make sure the function won't return to its point of invocation, one should call another noreturn function inside it and make sure that the latter is always run (in order to avoid undefined behavior) and doesn't cause UB itself.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
  • 1
    Thats true but you should read [this](https://stackoverflow.com/questions/10538291/what-is-the-point-of-noreturn) – kocica Aug 31 '17 at 12:39
  • @FilipKočica, this is _exactly_ what I was reading when you posted this comment – ForceBru Aug 31 '17 at 12:41
  • 3
    @user694733 I believe the core of the question is that the questioner expected `noreturn` to do something that prevented his code from returning, instead of `noreturn` actually placing restriction *on what the code he wrote is allowed to do under the standard*. – Andrew Henle Aug 31 '17 at 12:45
  • The chain of `noreturn` has to end somewhere, for example throwing an exception, calling `longjmp`, making a system call like `_exit(2)`, or doing something hacky with inline asm. But yes, unless you do something else that doesn't return, you should call another `noreturn` function (this includes `exit(3)` or `abort(3)`.) – Peter Cordes Sep 04 '17 at 09:36
6

no return function does not save the registers on the entry as it is not necessary. It makes the optimisations easier. Great for the scheduler routine for example.

See the example here: https://godbolt.org/g/2N3THC and spot the difference

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Actually, the only thing that can be optimized by `noreturn` is a few(!) bytes from the executable size: `noreturn` functions can never be part of the performance critical path in any code that's at least half sane. The whole construct is much more about silencing unnecessary warnings than about optimization. – cmaster - reinstate monica Aug 31 '17 at 13:20
  • @cmaster no attributes make code sane, only the knowledge of the coder. – 0___________ Aug 31 '17 at 13:33
  • That's not what I said. What I said is: If the code is at least half sane, that implies that there is zero performance impact of `noreturn`. That's the opposite direction of reasoning from what you seem to have understood. I would never say that use of `noreturn` makes code more or less sane. – cmaster - reinstate monica Aug 31 '17 at 13:49
  • `That's not what I said.` You have too complicated way of thinking for me. I do not catch that "half sane" thought – 0___________ Aug 31 '17 at 13:55
  • I simply had the problem that writing "`noreturn` functions can never be part of the performance critical path" would have been wrong: It is perfectly possible to write a program that contains `noreturn` calls in the performance critical path (by means of `longjmp()`). However, those programs will inevitably be braindead. The condition "code that's at least half sane" simply excludes those. Hope that clarifies things. (Sorry for my confusingly precise language.) – cmaster - reinstate monica Aug 31 '17 at 14:25
  • @cmaster: this answer's example of a kernel's `schedule()` function is a perfect example. It ends with a context switch to whatever should run next, and tuning the crap out of it is something that people really do. – Peter Cordes Sep 02 '17 at 01:24
  • Here's a Godbolt compiler explorer link using the diff-view feature (using a CPP macro and `-D` compiler option to build the same source with/without `_Noreturn`). https://godbolt.org/g/ocX4fS. Interestingly, gcc6.3 for x86 `-m32` still saves registers before using. I guess different targets have to implement this optimization separately? (BTW, in answers, prefer using non-shortened Godbolt compiler-explorer links, [to avoid link-rot](https://meta.stackoverflow.com/a/319594/224132). It's much better if you actually copy the asm you want to talk about into your answer, too). – Peter Cordes Sep 02 '17 at 01:30
  • Oops, left out `-Wall`. See https://godbolt.org/g/8QEPBT. I'm wondering if this "feature" is actually a bug, because I'm only seeing it on ARM. I simplified the code a lot by using a call to an external function to clobber registers, so it has to save/restore the function-arg register across that call on MIPS/PowerPC/ARM64/x86 as well. Only on ARM32 does it overwrite a call-preserved register without saving it first. ARM32's push/pop instructions are special (multiple regs in one insn), so it's plausible that ARM is different whether it's an optimization or a bug. – Peter Cordes Sep 02 '17 at 02:19
  • @PeterCordes `schedule()` is definitely not in any performance critical path: Context switches happen in time steps of around milliseconds, the "optimizations" we are talking about save time in the range of nanoseconds. That's a factor on the order of a million (very roughly). Just switching the virtual address space, and consequently invalidating the TLB is so expensive that its costs easily dwarve the gains of optimizing `noreturn` functions. And that's in kernel space. In user space, where most code is deployed, there is even less optimization potential. – cmaster - reinstate monica Sep 04 '17 at 08:59
  • @cmaster: You're right, `noreturn` only applies once you've decided that you *do* need to context switch, and that's once per call. Optimizing the scheduler is a thing, but that's for scalability with many tasks. I was also thinking of the voluntary pre-emption use-case, where a kernel function might call the scheduler while doing something slow. But if the scheduler might just return (or return after context switching to something else and back), then that function isn't `noreturn` after all. – Peter Cordes Sep 04 '17 at 09:27
  • So I agree, it's hard to imagine a sane case where it makes much of a performance difference. It could help for code-size if you have helper functions that throw exceptions. But if there's an exception throw/catch in a hot loop, you're doing something wrong. – Peter Cordes Sep 04 '17 at 09:30
2

TL:DR: It's a missed-optimization by gcc.


noreturn is a promise to the compiler that the function won't return. This allows optimizations, and is useful especially in cases where it's hard for the compiler to prove that a loop won't ever exit, or otherwise prove there's no path through a function that returns.

GCC already optimizes main to fall off the end of the function if func() returns, even with the default -O0 (minimum optimization level) that it looks like you used.

The output for func() itself could be considered a missed optimization; it could just omit everything after the function call (since having the call not return is the only way the function itself can be noreturn). It's not a great example since printf is a standard C function that is known to return normally (unless you setvbuf to give stdout a buffer that will segfault?)

Lets use a different function that the compiler doesn't know about.

void ext(void);

//static
int foo;

_Noreturn void func(int *p, int a) {
    ext();
    *p = a;     // using function args after a function call
    foo = 1;    // requires save/restore of registers
}

void bar() {
        func(&foo, 3);
}

(Code + x86-64 asm on the Godbolt compiler explorer.)

gcc7.2 output for bar() is interesting. It inlines func(), and eliminates the foo=3 dead store, leaving just:

bar:
    sub     rsp, 8    ## align the stack
    call    ext
    mov     DWORD PTR foo[rip], 1
   ## fall off the end

Gcc still assumes that ext() is going to return, otherwise it could have just tail-called ext() with jmp ext. But gcc doesn't tailcall noreturn functions, because that loses backtrace info for things like abort(). Apparently inlining them is ok, though.

Gcc could have optimized by omitting the mov store after the call as well. If ext returns, the program is hosed, so there's no point generating any of that code. Clang does make that optimization in bar() / main().


func itself is more interesting, and a bigger missed optimization.

gcc and clang both emit nearly the same thing:

func:
    push    rbp            # save some call-preserved regs
    push    rbx
    mov     ebp, esi       # save function args for after ext()
    mov     rbx, rdi
    sub     rsp, 8          # align the stack before a call
    call    ext
    mov     DWORD PTR [rbx], ebp     #  *p = a;
    mov     DWORD PTR foo[rip], 1    #  foo = 1
    add     rsp, 8
    pop     rbx            # restore call-preserved regs
    pop     rbp
    ret

This function could assume that it doesn't return, and use rbx and rbp without saving/restoring them.

Gcc for ARM32 actually does that, but still emits instructions to return otherwise cleanly. So a noreturn function that does actually return on ARM32 will break the ABI and cause hard-to-debug problems in the caller or later. (Undefined behaviour allows this, but it's at least a quality-of-implementation problem: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158.)

This is a useful optimization in cases where gcc can't prove whether a function does or doesn't return. (It's obviously harmful when the function does simply return, though. Gcc warns when it's sure a noreturn function does return.) Other gcc target architectures don't do this; that's also a missed optimization.

But gcc doesn't go far enough: optimizing away the return instruction as well (or replacing it with an illegal instruction) would save code size and guarantee noisy failure instead of silent corruption.

And if you're going to optimize away the ret, optimizing away everything that's only needed if the function will return makes sense.

Thus, func() could be compiled to:

    sub     rsp, 8
    call    ext
    # *p = a;  and so on assumed to never happen
    ud2                 # optional: illegal insn instead of fall-through

Every other instruction present is a missed optimization. If ext is declared noreturn, that's exactly what we get.

Any basic block that ends with a return could be assumed to never be reached.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847