Hi all, In GNU Emacs we use (in order to have all callee saved registers pushed on the stack before entering in the garbage collector) '__builtin_unwind_init'. If sibling calls optimization is performed the call to gc is moved after the register are popped again making '__builtin_unwind_init' ineffective. test.c====== void gc (); void foo (void) { __builtin_unwind_init (); gc (); } ======= Expected result: gcc -fno-optimize-sibling-calls -O2 test.c ======= foo: push rbp xor eax, eax mov rbp, rsp push r15 push r14 push r13 push r12 push rbx sub rsp, 8 call gc add rsp, 8 pop rbx pop r12 pop r13 pop r14 pop r15 pop rbp ret ======== gcc -02 test.c ======== foo: push rbp xor eax, eax mov rbp, rsp push r15 push r14 push r13 push r12 push rbx pop rbx pop r12 pop r13 pop r14 pop r15 pop rbp jmp gc ======== I understand '__builtin_unwind_init' it's undocumented and this can be categorized as a non-bug but AFAIU this optimization makes '__builtin_unwind_init' unusable for its scope. Another very possible scenario is that I don't understand correctly how '__builtin_unwind_init' is supposed to be used. I see this on trunk but this behavior is present since at least GCC13. Note: Original Emacs bug <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=63365> Thanks! Andrea
You can do the following as workaround: void __attribute__((optimize("no-optimize-sibling-calls"))) foo (void) { __builtin_unwind_init (); gc (); }
Hi Richard, thanks, yes I tried that already, the trouble is that our code looks more like this: ========= extern void flush_stack_call_func1 (void (*func) (void *arg), void *arg); extern void mark_threads (void); extern void mark_threads_callback (void *ignore); static inline void flush_stack_call_func (void (*func) (void *arg), void *arg) { __builtin_unwind_init (); flush_stack_call_func1 (func, arg); } void mark_threads (void) { flush_stack_call_func (mark_threads_callback, ((void *)0)); } [...] ========= Because we have several places where 'flush_stack_call_func' is used. Unfortunately as 'flush_stack_call_func' gets inlined the trick does not work. I'm not ecstatic at making 'flush_stack_call_func' not inlinable nor at marking all the functions where it is called with the attribute, but we'll have to find a way anyway I guess. Thanks Andrea
Placing an empty asm is sufficient to prevent undesired tailcalls: void gc (); void foo (void) { __builtin_unwind_init (); gc (); asm (""); }