Bug 105469 - [13/14/15/16 Regression] "execution reached an unreachable program point" with -flto since r5-7027-g0b986c6ac777aa4e
Summary: [13/14/15/16 Regression] "execution reached an unreachable program point" wit...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: middle-end (show other bugs)
Version: 13.0
: P2 normal
Target Milestone: 13.5
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2022-05-03 19:58 UTC by Rémi Coulom
Modified: 2025-07-11 09:10 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2022-09-14 00:00:00


Attachments
compressed preprocessed source file that produces the bug (118.37 KB, application/x-bzip)
2022-05-03 19:58 UTC, Rémi Coulom
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Rémi Coulom 2022-05-03 19:58:19 UTC
Created attachment 52926 [details]
compressed preprocessed source file that produces the bug

Operating system and compiler versions:

Ubuntu 20.04: tested with g++ version 7.5.0, 8.4.0, 9.4.0, 10.3.0, 11.1.0, and 13.0 (compiled from git repository this week-end with default options). Also in 32-bit cygwin with 11.2.0. All produce the same problem.

Step to reproduce the problem:

The program works well when compiling without -flto:
$ g++ -Wall -Wextra -Wno-unused-parameter -fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations -O3 -fsanitize=unreachable -o repro repro.ii
$ ./repro
-> Yeah, no bug!

adding -flto makes it crash:
$ g++ -Wall -Wextra -Wno-unused-parameter -fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations -O3 -flto -fsanitize=unreachable -o repro repro.ii
$ ./repro
-> repro.cpp:1345:24: runtime error: execution reached an unreachable program point

I added some trace to the code, that shows a little more information. I tried to reduce the size of the code as much pas possible, but it was very difficult because many changes can make the problem appear or disappear. In particular, not catching exceptions in main makes the bug disappear.

I searched the web for this error message, and found this interesting link:
https://deathandthepenguinblog.wordpress.com/2021/02/13/undefined-behaviour-and-nasal-demons-or-do-not-meddle-in-the-affairs-of-optimizers/
What is described there feels similar to what is happening to me. So I searched hard for undefined behaviour in my code.

I humbly admit that this crash may very well be a bug in my code instead of a bug in g++, but after several days of struggle, I still really can make no sense of what is happening.

If g++ detected undefined behavior in my code and is using this to remove some part of the code or perform optimizations, then I feel it should tell me. I want to know, because undefined behavior is much more likely to be a bug that needs fixing rather than a good optimization opportunity.
Comment 1 Richard Biener 2022-05-04 06:28:16 UTC
I can reproduce what you observe with -O3 -fwhole-program -fsanitize=unreachable
(without the sanitize it gets into an infinite loop).  -fno-ipa-cp-clone avoids
the issue (whatever it is exactly).

Can you attach un-preprocessed source as well?  You seem to be not using any non-standard header files.
Comment 2 Rémi Coulom 2022-05-04 07:15:10 UTC
(In reply to Richard Biener from comment #1)
> I can reproduce what you observe with -O3 -fwhole-program
> -fsanitize=unreachable
> (without the sanitize it gets into an infinite loop).  -fno-ipa-cp-clone
> avoids
> the issue (whatever it is exactly).
> 
> Can you attach un-preprocessed source as well?  You seem to be not using any
> non-standard header files.

Thanks for checking my code.

The code is available on github:
https://github.com/Remi-Coulom/joedb

Necessary files to replicate the bug can be found there:
https://github.com/Remi-Coulom/joedb/tree/master/doc/source/tutorial/gcc_bug

repro.cpp and all_includes.h should be enough.

I produced repro.cpp by preprocessing bug.cpp and pruning a lot of code. I suppose repro.cpp should be enough for your purpose. If you ever wish to compile bug.cpp you'll have to compile joedb first by running "./generate.sh" in doc/source/tutorial.
Comment 3 Martin Liška 2022-05-04 08:54:13 UTC
Reducing that right now..
Comment 4 Jakub Jelinek 2022-05-04 09:28:02 UTC
-fno-devirtualize also makes it go away.
Without that, the __builtin_unreachable() call in question is created in:
#0  0x00007ffff7ad4424 in __memset_avx2_unaligned_erms () from /lib64/libc.so.6
#1  0x0000000000fee86e in ggc_internal_alloc (size=144, f=0x0, s=0, n=1) at ../../gcc/ggc-page.cc:1400
#2  0x00000000012bf869 in ggc_internal_cleared_alloc (size=144, f=0x0, s=0, n=1) at ../../gcc/ggc-common.cc:114
#3  0x0000000000c9f446 in ggc_internal_cleared_alloc (s=144) at ../../gcc/ggc.h:147
#4  0x00000000012d69e5 in ggc_alloc_cleared_gimple_statement_stat (s=144) at ../../gcc/ggc.h:329
#5  0x00000000012cc892 in gimple_alloc (code=GIMPLE_CALL, num_ops=5) at ../../gcc/gimple.cc:156
#6  0x00000000012d00ae in gimple_copy (stmt=<gimple_call 0x7fffe888af30>) at ../../gcc/gimple.cc:1913
#7  0x000000000183e098 in remap_gimple_stmt (stmt=<gimple_call 0x7fffe888af30>, id=0x7fffffffd1e0) at ../../gcc/tree-inline.cc:1862
#8  0x000000000183e996 in copy_bb (id=0x7fffffffd1e0, bb=<basic_block 0x7fffe74a6548 (5)>, num=..., den=...) at ../../gcc/tree-inline.cc:2051
#9  0x0000000001841ee7 in copy_cfg_body (id=0x7fffffffd1e0, entry_block_map=<basic_block 0x7fffe6c40af8 (21)>, exit_block_map=<basic_block 0x7fffe71bd548 (212)>, 
    new_entry=<basic_block 0x0>) at ../../gcc/tree-inline.cc:3085
#10 0x0000000001842b2a in copy_body (id=0x7fffffffd1e0, entry_block_map=<basic_block 0x7fffe6c40af8 (21)>, exit_block_map=<basic_block 0x7fffe71bd548 (212)>, 
    new_entry=<basic_block 0x0>) at ../../gcc/tree-inline.cc:3338
#11 0x0000000001847993 in expand_call_inline (bb=<basic_block 0x7fffe6c40af8 (21)>, stmt=<gimple_call 0x7fffe83ea870>, id=0x7fffffffd1e0, to_purge=0x7fffffffd1c0)
    at ../../gcc/tree-inline.cc:5123
when copying the
  OBJ_TYPE_REF(_9;(struct Writable)writable_7(D)->10B) (writable_7(D), 0);
call (line 1345 in repro.cpp) and later on this one is replaced by
__builtin_unreachable:
#0  gimple_call_set_fndecl (gs=0x7fffe8621090, decl=<function_decl 0x7fffea34ec00 __builtin_unreachable>) at ../../gcc/gimple.h:3198
#1  0x00000000010b7521 in cgraph_edge::redirect_call_stmt_to_callee (
    e=<cgraph_edge* 0x7fffe74a6750 (<cgraph_node * 0x7fffe7ae6000 "local_concurrency"/1538> -> <cgraph_node * 0x7fffe737faa0 "__builtin_unreachable"/2408>)>)
    at ../../gcc/cgraph.cc:1555
#2  0x0000000001841af2 in redirect_all_calls (id=0x7fffffffd1e0, bb=<basic_block 0x7fffe74a68f0 (220)>) at ../../gcc/tree-inline.cc:3002
#3  0x00000000018423c7 in copy_cfg_body (id=0x7fffffffd1e0, entry_block_map=<basic_block 0x7fffe6c40af8 (21)>, exit_block_map=<basic_block 0x7fffe71bd548 (212)>, 
    new_entry=<basic_block 0x0>) at ../../gcc/tree-inline.cc:3173
#4  0x0000000001842b2a in copy_body (id=0x7fffffffd1e0, entry_block_map=<basic_block 0x7fffe6c40af8 (21)>, exit_block_map=<basic_block 0x7fffe71bd548 (212)>, 
    new_entry=<basic_block 0x0>) at ../../gcc/tree-inline.cc:3338
#5  0x0000000001847993 in expand_call_inline (bb=<basic_block 0x7fffe6c40af8 (21)>, stmt=<gimple_call 0x7fffe83ea870>, id=0x7fffffffd1e0, to_purge=0x7fffffffd1c0)
    at ../../gcc/tree-inline.cc:5123
#6  0x000000000184874c in gimple_expand_calls_inline (bb=<basic_block 0x7fffe6c40af8 (21)>, id=0x7fffffffd1e0, to_purge=0x7fffffffd1c0) at ../../gcc/tree-inline.cc:5318
#7  0x0000000001848f1c in optimize_inline_calls (fn=<function_decl 0x7fffe7adca00 local_concurrency>) at ../../gcc/tree-inline.cc:5490
#8  0x000000000141b1ee in inline_transform (node=<cgraph_node * 0x7fffe7ae6000 "local_concurrency"/1538>) at ../../gcc/ipa-inline-transform.cc:790
Comment 5 Martin Liška 2022-05-04 10:30:19 UTC
Reduced test-case:

namespace std {
template <class> struct char_traits;
template <typename _CharT, typename = char_traits<_CharT>> class basic_ostream;
struct ios_base {
  struct Init {
    Init();
  };
};
template <typename, typename> struct basic_ostream {
  void operator<<(const void *);
};
extern basic_ostream<char> cerr;
ios_base::Init __ioinit;
} // namespace std
enum Commit_Level { no_commit };
struct Writable {
  virtual void drop_fieldtimestampvalid_data() {}
  virtual void checkpoint(Commit_Level) {}
};
struct Posix_File {
  Posix_File();
  ~Posix_File();
};
Posix_File::Posix_File() {}
Posix_File::~Posix_File() {}
struct Readonly_Journal {
  Readonly_Journal();
  void play_until_checkpoint(Writable &writable) {
    writable.checkpoint(no_commit);
    std::cerr << "";
  }
};
Readonly_Journal::Readonly_Journal() {}
struct Writable_Journal : Readonly_Journal {
  Writable_Journal();
  ~Writable_Journal();
};
Writable_Journal::Writable_Journal() {}
Writable_Journal::~Writable_Journal() {}
struct Connection {
  virtual void push(Readonly_Journal &, long, bool) = 0;
};
template <typename Client_Data> struct Trans_NS_joedb_Client {
  Connection &connection;
  Client_Data data;
  Trans_NS_joedb_Client(Connection &, Posix_File file)
      : connection(connection), data(connection, file) {
    std::cerr << &data;
    data.update();
  }
};
struct Local_Connection : Connection {
  Posix_File file;
  void push(Readonly_Journal &, long, bool) {}
};
struct Generic_File_Database : Writable {
  Writable_Journal journal;
  Generic_File_Database();
  void checkpoint(Commit_Level) {}
};
struct Client_Data : Generic_File_Database {
  Client_Data(Connection &, Posix_File) {}
  void update() { journal.play_until_checkpoint(*this); }
};
struct Client : Trans_NS_joedb_Client<Client_Data> {
  Posix_File Client_local_file;
  Client() : Trans_NS_joedb_Client(connection, Client_local_file) {}
};
Generic_File_Database::Generic_File_Database() {}
struct Buggy_Client : Local_Connection, Client {};

int main() { Buggy_Client(); }
Comment 6 Martin Liška 2022-05-04 10:33:58 UTC
g++ repro.ii -fsanitize=undefined -O3 -fwhole-program && ./a.out
0x7fffffffdb38repro.ii:29:24: runtime error: execution reached an unreachable program point
    #0 0x401300 in main (/home/marxin/Programming/testcases/a.out+0x401300)
    #1 0x7ffff751f62f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #2 0x7ffff751f6ef in __libc_start_main_impl ../csu/libc-start.c:392
    #3 0x401384 in _start (/home/marxin/Programming/testcases/a.out+0x401384)
Comment 7 Martin Liška 2022-05-04 10:34:49 UTC
Backtrace with debuginfo:

$ g++ repro.ii -fsanitize=undefined -O3 -fwhole-program -g && ./a.out
0x7fffffffdb38repro.ii:29:24: runtime error: execution reached an unreachable program point
    #0 0x401300 in Readonly_Journal::play_until_checkpoint(Writable&) /home/marxin/Programming/testcases/repro.ii:29
    #1 0x401300 in Client_Data::update() /home/marxin/Programming/testcases/repro.ii:63
    #2 0x401300 in Trans_NS_joedb_Client<Client_Data>::Trans_NS_joedb_Client(Connection&, Posix_File) /home/marxin/Programming/testcases/repro.ii:49
    #3 0x401300 in Client::Client() /home/marxin/Programming/testcases/repro.ii:67
    #4 0x401300 in Buggy_Client::Buggy_Client() /home/marxin/Programming/testcases/repro.ii:70
    #5 0x401300 in main /home/marxin/Programming/testcases/repro.ii:72
    #6 0x7ffff751f62f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #7 0x7ffff751f6ef in __libc_start_main_impl ../csu/libc-start.c:392
    #8 0x401384 in _start (/home/marxin/Programming/testcases/a.out+0x401384)
Comment 8 Richard Biener 2022-05-04 10:44:21 UTC
The original testcase has

   void lock() override = 0;
   void unlock() override = 0;
   void lock() final override
   void unlock() final override

and a few more override.  The reduced one has nothing like that (but maybe it's misreduced).  Removing 'final' doesn't avoid the issue though.

It might be this is a bad interaction of speculative devirt and inlining,
-fno-devirtualize-speculatively is enough to prevent the bug.
Comment 9 Martin Liška 2022-05-04 10:50:03 UTC
It's the 1st devirtualization that happens:

Polymorphic call context combine:    Outer type:struct Buggy_Client offset 192
With context:                        Outer type (dynamic):struct Trans_NS_joedb_Client (maybe in construction) offset 64 Speculative outer type:struct Client_Data at offset 0
Second type is base of first
First context does not permit base -> invalid
No devirtualization target in play_until_checkpoint.constprop/106
Introduced new external node (void __builtin_unreachable()/107).
***dbgcnt: lower limit 1 reached for devirt.***
***dbgcnt: upper limit 1 reached for devirt.***
Comment 10 Martin Liška 2022-05-04 10:53:38 UTC
Maybe interacts with IPA CP -fno-ipa-cp helps here.
Comment 11 Martin Liška 2022-05-04 11:01:05 UTC
Likely started with r5-7027-g0b986c6ac777aa4e, it is looping before the revision.
Comment 12 Jakub Jelinek 2022-05-04 11:01:50 UTC
On the original testcase I see
First context does not permit base -> invalid
No devirtualization target in void joedb::Readonly_Journal::play_until_checkpoint.constprop(joedb::Writable&)/2704
It is unclear why play_until_checkpoint would be cloned, it doesn't have any args that would be IPA-CP propagated.  The checkpoint virtual call has, but until it is devirtualized we can't optimize it.
Comment 13 Andrew Pinski 2022-05-05 04:50:26 UTC
(In reply to Martin Liška from comment #11)
> Likely started with r5-7027-g0b986c6ac777aa4e, it is looping before the
> revision.

No, that revision just caused -fsanitize=unreachable to catch it (see PR 61591).
Comment 14 Rémi Coulom 2022-05-11 12:47:31 UTC
Thanks for investigating the bug.

Can you recommend a workaround at this point?

I don't want to tell users of the library to change their compilation options, so I tried this in class Client_Data:

#ifdef __GNUC__
// Workaround for gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105469
   __attribute__ ((noinline))
#endif
   void update()
   {
    ...
   }

It seems to be working OK for the moment, but all kinds of random changes to the code seemed to make the bug appear or disappear in the past, so I am not completely satisfied with this situation. Can you explain the bug better? Do you have any recommendation?

Thanks.
Comment 15 Richard Biener 2022-09-14 13:08:50 UTC
Re-confirmed on todays trunk.  IPA folks - can you please have a look here?
Comment 16 Jakub Jelinek 2023-01-18 16:46:02 UTC
Can't reproduce the __builtin_unreachable on the trunk.
Bisection shows that
cc1plus -quiet -fsanitize=undefined -O3 -fwhole-program -fdump-tree-optimized pr105469.C; grep unreachable pr105469.C.*
stopped printing anything with
r13-4686-g095a13eda2caf6842096a3ab78b2081c50fe8799
Martin, is that a real fix for this or it just went latent?
Comment 17 Jakub Jelinek 2023-01-18 16:47:01 UTC
(In reply to Jakub Jelinek from comment #16)
> Can't reproduce the __builtin_unreachable on the trunk.

With the https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105469#c5 testcase, that is.
Haven't tried the original.
Comment 18 Jan Hubicka 2023-01-18 21:30:41 UTC
It should just make any bug to go latent.  It surprises me it makes any difference given that things not cloned by ipa-cp should be all handled by ipa-sra.
Comment 19 Martin Jambor 2023-01-19 13:25:06 UTC
(In reply to Jakub Jelinek from comment #16)
> Martin, is that a real fix for this or it just went latent?

Not a fix, the bug mst be latent.  But it is surprising so I'll have a look what happened too.
Comment 20 Richard Biener 2023-07-07 10:43:15 UTC
GCC 10 branch is being closed.
Comment 21 Richard Biener 2024-07-19 13:16:21 UTC
GCC 11 branch is being closed.
Comment 22 Richard Biener 2025-07-11 09:10:55 UTC
GCC 12 branch is being closed.