Compiling with -flto and -nostartfiles results in no program at all
As @MaximilanGerhardt wrote in his comment. LTO was optimizing away too much of the code. Usual programs start with the interrupt vector table that in case of a regular reset or startup point to the main function. That is why the compiler knows that main has an entry point and is used. The actual program starts at the table.

Removing  the interrupt vector table with -nostartfiles causes the compiler not to know that main is used at all and optimizes it away.

To give the compiler a hint __attribute__((used)) has to be placed at all methods that have to stay and there is no code path leading to them otherwise.