Tell gcc that a function call will not return

Tom picture Tom · Aug 20, 2014 · Viewed 9.8k times · Source

I am using C99 under GCC.

I have a function declared static inline in a header that I cannot modify.

The function never returns but is not marked __attribute__((noreturn)).

How can I call the function in a way that tells the compiler it will not return?

I am calling it from my own noreturn function, and partly want to suppress the "noreturn function returns" warning but also want to help the optimizer etc.

I have tried including a declaration with the attribute but get a warning about the repeated declaration.

I have tried creating a function pointer and applying the attribute to that, but it says the function attribute cannot apply to a pointed function.

Answer

Antoine picture Antoine · Aug 20, 2014

From the function you defined, and which calls the external function, add a call to __builtin_unreachable which is built into at least GCC and Clang compilers and is marked noreturn. In fact, this function does nothing else and should not be called. It's only here so that the compiler can infer that program execution will stop at this point.

static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }

__attribute__((noreturn)) void your_function() {
    external_function();     // the compiler thinks execution may continue ...
    __builtin_unreachable(); // ... and now it knows it won't go beyond here
}

Edit: Just to clarify a few points raised in the comments, and generally give a bit of context:

  • A function has has only two ways of not returning: loop forever, or short-circuit the usual control-flow (e.g. throw an exception, jump out of the function, terminate the process, etc.)
  • In some cases, the compiler may be able to infer and prove through static analysis that a function will not return. Even theoretically, this is not always possible, and since we want compilers to be fast only obvious/easy cases are detected.
  • __attribute__((noreturn)) is an annotation (like const) which is a way for the programmer to inform the compiler that he's absolutely sure a function will not return. Following the trust but verify principle, the compiler tries to prove that the function does indeed not return. If may then issue an error if it proves the function may return, or a warning if it was not able to prove whether the function returns or not.
  • __builtin_unreachable has undefined behaviour because it is not meant to be called. It's only meant to help the compiler's static analysis. Indeed the compiler knows that this function does not return, so any following code is provably unreachable (except through a jump).

Once the compiler has established (either by itself, or with the programmer's help) that some code is unreachable, it may use this information to do optimizations like these:

  • Remove the boilerplate code used to return from a function to its caller, if the function never returns
  • Propagate the unreachability information, i.e. if the only execution path to a code points is through unreachable code, then this point is also unreachable. Examples:
    • if a function does not return, any code following its call and not reachable through jumps is also unreachable. Example: code following __builtin_unreachable() is unreachable.
    • in particular, it the only path to a function's return is through unreachable code, the function can be marked noreturn. That's what happens for your_function.
    • any memory location / variable only used in unreachable code is not needed, therefore settings/computing the content of such data is not needed.
    • any computations which is probably (1) unnecessary (previous bullet) and (2) has no side effects (such as pure functions) may be removed.

Illustration:

  • The call to external_function cannot be removed because it might have side-effects. In fact, it probably has at least the side effect of terminating the process!
  • The return boiler plate of your_function may be removed

Here's another example showing how code before the unreachable point may be removed

int compute(int) __attribute((pure)) { return /* expensive compute */ }
if(condition) {
    int x = compute(input); // (1) no side effect => keep if x is used
                            // (8) x is not used  => remove
    printf("hello ");       // (2) reachable + side effect => keep
    your_function();        // (3) reachable + side effect => keep
                            // (4) unreachable beyond this point
    printf("word!\n");      // (5) unreachable => remove
    printf("%d\n", x);      // (6) unreachable => remove
                            // (7) mark 'x' as unused
} else {
                            // follows unreachable code, but can jump here
                            // from reachable code, so this is reachable
   do_stuff();              // keep
}