I wanted to check if GCC makes sure extra code generated for running destructors when exceptions are thrown is put in a cold section of the binary, to keep those instructions away from the "happy path" and avoid contributing to instruction cache pressure. So I made the example below (Godbolt link) where GCC can't tell if an exception is going to be thrown. Sure enough I see a separate buzz(int) [clone .cold.0]:
label in the assembly. But there's this curious artifact above it:
.L9:
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
mov eax, ebx
add rsp, 16
imul eax, ebx
add eax, ebx
pop rbx
ret
mov rbx, rax
jmp .L3
buzz(int) [clone .cold.0]:
.L3:
mov eax, DWORD PTR [rsp+12]
...
Notice the ret
instruction is follow by a jmp
into the cold area. But instructions after ret
that aren't a jump target (no label) should never run. I thought maybe it could be for instruction alignment purposes, but following with a jump into the cold section seems like too weird a coincidence. What's going on? Why are these instructions here? Maybe something to do with the protocol for how unwinding works requires it to be there, and the exception unwind table makes it a jump target, but it's just unlabeled? But if that's the case why not jump straight to the cold section?
C++ code:
#include <stdio.h>
extern int global;
struct Foo
{
Foo(int x)
: x(x)
{}
~Foo() {
if(x % global == 0) {
printf("Wow.");
}
}
int x;
};
void bar(Foo& f); // compiler can't see impl, has to assume could throw
// Type your code here, or load an example.
int buzz(int num) {
{
Foo foo(3);
bar(foo);
}
return num * num + num;
}