https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158
Bug ID: 82158 Summary: _Noreturn functions that do return clobber caller's registers on ARM32 (but not other arches) Product: gcc Version: 7.1.0 URL: https://godbolt.org/g/GhW4b8 Status: UNCONFIRMED Keywords: wrong-code Severity: normal Priority: P3 Component: target Assignee: unassigned at gcc dot gnu.org Reporter: peter at cordes dot ca Target Milestone: --- Target: arm*-*-* ARM32 clobbers a call-preserved register in a function which does actually return. (It was declared _Noreturn, but gcc chooses to make a function which returns instead of crashing). Not sure if this is a feature or bug (came up in https://stackoverflow.com/a/45982153/224132). Probably a bug, since saving registers in a noreturn function is potentially useful for backtraces (and gcc avoids tailcall with noreturn for that reason), even if exceptions are disabled so stack-unwinding doesn't need them. And this is only happening on ARM32, not the others I looked at: ARM64, MIPS, MIPS64, PowerPC64, MSP430, x86 -m32, or x86 -m64. (But ARM's multi-operand-at-once POP is probably handled specially, so that could explain the difference...) Anyway, in a function declared noreturn which actually *does* return, ARM32 still does the optimization of clobbering "call-preserved" even though it also generates code to return instead of just falling off the end of the function. This is C undefined behaviour so it's legal, but is this desirable? gcc warns about it (even without -Wall), but it's so likely to cause hard-to-debug problems that I'd suggest erroring by default, or not doing the optimization. // https://godbolt.org/g/GhW4b8 for gcc6.3, also tested locally with gcc7.1 void ext(void); ATTRIBUTE void foo(int *p, int y) { ext(); *p = y; // then use args that had to survive a call } On ARM32 with gcc7.1 -O3 -DATTRIBUTE=_Noreturn produces this: 7 : <source>:7:1: warning: 'noreturn' function does return foo: @ Function supports interworking. @ Volatile: function does not return. # This line not present without _Noreturn @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 push {r4, lr} mov r5, r1 mov r4, r0 bl ext str r5, [r4] pop {r4, lr} bx lr With ATTRIBUTE= (empty), we get push/pop {r4, r5, r6, lr} and things are otherwise the same. See https://godbolt.org/g/hYnrrD for a diff.