It is about time I write a second post on GCC Builtins. In the last installment we learned how to help our branch predictor figure out which branches to speculatively execute. This time we will learn about how to tell our compiler that a segment of code will never be reached if it cannot figure that out on its own.
I’ve already written a bit about when and when not to use these very specific and obscure features. The short answer is you probably don’t want to use them at all unless you really want to; you’re also limiting yourself to GCC, which might or might not be okay for you. That’s all I am going to say about that matter this time. Consider yourselves warned!
And now, without further ado, let’s tell our compiler to look the other way!
Nothing to see here, move along
There are times when our compiler cannot possibly know about control
flow. Consider the example of e, an editor I wrote about
previously. The main event loop looks more or less like this:
void event_loop(editor e) {
while (1) {
redraw_screen(e);
e = process_key(e);
}
}
int main() {
editor e = setup_editor(e);
event_loop(e);
}
e’s event loop.
This loop obviously never terminates. Instead, my library calls
exit() internally when certain keys are pressed, and a
registered cleanup function is called. This is all well and good, but I
declared my main() function to return an int.
GCC will helpfully complain about this—provided we turn on certain
warning levels or turn on -Wreturn-type manually:
main.c: in function 'main':
t.c:61:1: warning: control reaches end of
non-void function [-Wreturn-type]
If I put __builtin_unreachable(); at the end of the
function, though, the error vanishes. To be clear: this has implications
on the generated machine code. It will prevent GCC from creating code to
return from the function. More importantly, though, it means fewer
warnings and clear, stated intent, which is what makes me write it.
It is also useful if we’re writing inline assembly that performs
jumps. I’m not sure how many of my readers do that, but I’m sure all of
them are eager to know that __builtin_unreachable comes to
the rescue there as well. Let me give you a contrived example:
void helper() {
asm("jump_towards_me:");
puts("hi!");
}
int main() {
asm("jmp jump_towards_me");
}
Don’t do this at home. This is not good. If you need to do something
like that, you’re probably better off using setjump/longjump,
which I’ve used before for implementing tail recursion. But it is
tedious, brittle, and best avoided. Still, isn’t it a great comfort that
even in this case we can make the error go away by inserting a single
__builtin_unreachable?
Builtins are amazing!
The more I read about them, the more I appreciate GCC’s builtins, even if I sacrifice portability by using them. They’re just damn handy.
Next time, we will explore how to minimize cache-miss latency by using a GCC builtin, so stay tuned!