Test case (this is C code converted from a Go program): extern void exit (int) __attribute ((__noreturn__)); struct s { int f1; unsigned int f2; }; void i1 (void *p) { } void quit (const char *) __attribute ((__noreturn__, __noinline__)); void quit (const char *s) { exit (1); } #define c0 1 #define c1 2 #define c2 4 #define c3 3 static void f3 (unsigned int *, int, int) __attribute__ ((__noinline__)); static void f3 (unsigned int *p, int v, int v2) { quit ("f3"); } void f2 (struct s *m, int v) { if (((v + c0) & c0) == 0) quit ("fail1"); if ((v & c2) == 0) { int v2 = v; lab: { _Bool f = (v2 >> c3) == 0; if (! f) f = (v2 & (c0 | c1 | c2)) != 0; if (f) return; v = (v2 - (1 << c3)) | c1; if (__atomic_compare_exchange_n (&m->f1, &v2, v, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) != 0) { f3 (&m->f2, 0, 1); return; } v2 = m->f1; } goto lab; } else f3 (&m->f2, 1, 1); } void f1 (struct s *) __attribute__ ((__noinline__)); void f1 (struct s *m) { int v; if (0) i1 ((void*) m); v = __atomic_add_fetch (&m->f1, -c0, __ATOMIC_SEQ_CST); if (v != 0) f2 (m, v); } int main () { struct s m; m.f1 = c0 + (1 << c3); m.f2 = 0; f1 (&m); } > gcc -o foo -g -O2 -fno-optimize-sibling-calls foo.c > gdb ./foo <gdb stuff> (gdb) break f3 Breakpoint 1 at 0x4005b0: file /home/iant/foo.c, line 33. (gdb) r Starting program: /home/iant/gcc/gccgo4-objdir/foo Breakpoint 1, f3 (v2=1, v=0, p=<optimized out>) at /home/iant/foo.c:33 33 quit ("f3"); (gdb) where #0 f3 (v2=1, v=0, p=<optimized out>) at foo.c:33 #1 0x00000000004005ea in f2 (v=<optimized out>, m=<optimized out>) at foo.c:56 #2 f2 (m=<optimized out>, v=<optimized out>) at foo.c:37 #3 0x0000000000400629 in f1 (m=m@entry=0x7fffffffe308) at foo.c:78 #4 0x0000000000400497 in main () at foo.c:87 Note how in the stack backtrace f2 appears twice although it does not call itself. This appears to be happening because pass_split_functions splits f2 into two functions, f2 and f2.part.0. Then the ipa-inline pass inlines the split function back into its single caller. We are left with a single function that has debug info showing an inlined call of itself. gdb prints the resulting stack trace showing the inlined call, but this doesn't make sense since there is only one function and one stack frame here. There is no user visible inlining. This happens with GCC 10 (current trunk) but does not happen with GCC 9. (This causes trouble with the original Go code, because Go expects to be able to reliably unwind the stack. In this case there appears to be no marker letting us know that the inlined call is to a split function that should be ignored, so Go sees f2 calling itself, leading to incorrect stack unwinding.)
Author: ian Date: Thu Sep 5 04:12:30 2019 New Revision: 275396 URL: https://gcc.gnu.org/viewcvs?rev=275396&root=gcc&view=rev Log: PR tree-optimization/91663 * go-lang.c (go_langhook_post_options): Clear flag_partial_inlining. Modified: trunk/gcc/go/ChangeLog trunk/gcc/go/go-lang.c
@Ian: Can we close this?
I still see the problem with near-tip GCC. Is there any reason to think that this problem is fixed? (Note that I'm reporting what I consider to be a bug in the C compiler. The patch attached to this PR is the Go frontend working around the problem in the middle-end. It's not a fix for the bug.)