From 879fe88f1fe1ada5c0f8aa4594e72cfaa89f199a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Oct 2026 10:27:48 -0400 Subject: [PATCH 1/3] Add reproducer test set --- tests/tests/regressions/gh-175/other.C | 5 +++++ tests/tests/regressions/gh-175/test.mft.cpp | 13 +++++++++++++ tests/tests/regressions/gh-175/twice.h | 6 ++++++ 3 files changed, 24 insertions(+) create mode 100644 tests/tests/regressions/gh-175/other.C create mode 100644 tests/tests/regressions/gh-175/test.mft.cpp create mode 100644 tests/tests/regressions/gh-175/twice.h diff --git a/tests/tests/regressions/gh-175/other.C b/tests/tests/regressions/gh-175/other.C new file mode 100644 index 0000000000..c84906aca3 --- /dev/null +++ b/tests/tests/regressions/gh-175/other.C @@ -0,0 +1,5 @@ +#include "twice.h" +using fn = int (*)(int); +using move_fn = int &&(*)(int &); +fn other() { return &twice; } +move_fn other_move() { return &move_it; } diff --git a/tests/tests/regressions/gh-175/test.mft.cpp b/tests/tests/regressions/gh-175/test.mft.cpp new file mode 100644 index 0000000000..51e48887ea --- /dev/null +++ b/tests/tests/regressions/gh-175/test.mft.cpp @@ -0,0 +1,13 @@ +//type:rp +//options:--c++17 --g++:--c++17 --g++ -tused:--c++17 --clang +//source_files:twice.h other.C +#include "twice.h" +using fn = int (*)(int); +using move_fn = int &&(*)(int &); +fn other(); +move_fn other_move(); +int main() { + int i = 1; + return (&twice == other() && twice(1) == 2 && + &move_it == other_move() && move_it(i) == 1) ? 0 : 1; +} diff --git a/tests/tests/regressions/gh-175/twice.h b/tests/tests/regressions/gh-175/twice.h new file mode 100644 index 0000000000..b793928287 --- /dev/null +++ b/tests/tests/regressions/gh-175/twice.h @@ -0,0 +1,6 @@ +[[gnu::always_inline]] inline int twice(int x) { return x + x; } + +// Like std::move and std::forward in libstdc++ 15. +template [[gnu::always_inline]] constexpr T &&move_it(T &t) { + return static_cast(t); +} From 3247720e6a42945100b38f33940f5bde9e0f0162 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Oct 2026 10:28:26 -0400 Subject: [PATCH 2/3] Always mark affected functions as __weak__ and clear force inline flag --- src/c_gen_be.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/c_gen_be.c b/src/c_gen_be.c index 61f81fd44a..644f356700 100644 --- a/src/c_gen_be.c +++ b/src/c_gen_be.c @@ -11086,20 +11086,26 @@ if this routine has a body (dump nothing if it has no body). (a_storage_class)sc_unspecified, "dump_routine_decl: rout without defn in comdat"); if (gcc_or_clang_is_generated_code_target -#if GNU_EXTENSIONS_ALLOWED - && !rout->always_inline /* gcc gives an error on weak functions with - the always_inline attribute (because they - can change at link time). */ -#if !LOWER_IFUNC +#if GNU_EXTENSIONS_ALLOWED && !LOWER_IFUNC && !rout->is_ifunc /* gcc doesn't allow ifunc to be weak, so suppress the weak attribute (though this may result in multiple-definition errors in some cases). */ -#endif /* !LOWER_IFUNC */ -#endif /* GNU_EXTENSIONS_ALLOWED */ +#endif /* GNU_EXTENSIONS_ALLOWED && !LOWER_IFUNC */ ) { /* GCC does not support COMDAT, but it does support weak, which provides a sufficient approximation. */ +#if GNU_EXTENSIONS_ALLOWED + if (rout->always_inline && gcc_is_generated_code_target) { + /* gcc gives an error on calls to a weak function with the + always_inline attribute (because the definition could be + replaced at link time). Without the weak attribute each + translation unit that defines the routine contains a conflicting + external definition of it, so suppress the always_inline + attribute instead (Clang accepts both). */ + rout->always_inline = FALSE; + } /* if */ +#endif /* GNU_EXTENSIONS_ALLOWED */ write_tok_str(" __attribute__((__weak__))"); is_marked_weak = TRUE; } /* if */ From bbf81d06784e4eb2b2ed4cc31998b8176537e4da Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Oct 2026 10:29:30 -0400 Subject: [PATCH 3/3] Update changes and run files --- src/Changes | 29 +++++++++++++++++++ .../gh-175/.test.rto/default.1.1.txt | 3 ++ .../gh-175/.test.rto/default.1.2.txt | 1 + .../gh-175/.test.rto/default.2.1.txt | 3 ++ .../gh-175/.test.rto/default.2.2.txt | 1 + .../gh-175/.test.rto/default.3.1.txt | 3 ++ .../gh-175/.test.rto/default.3.2.txt | 1 + 7 files changed, 41 insertions(+) create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.1.1.txt create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.1.2.txt create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.2.1.txt create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.2.2.txt create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.3.1.txt create mode 100644 tests/tests/regressions/gh-175/.test.rto/default.3.2.txt diff --git a/src/Changes b/src/Changes index 14d036ee11..56e0b8b095 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,34 @@ Version 7.1 +10/7/26 [GH #175] +C-generating back end: always_inline routines defined in several objects + +When a routine placed in a COMDAT group also had the GNU "always_inline" +attribute, the C-generating back end suppressed the __weak__ attribute that +it otherwise uses to approximate the COMDAT group (see the entry for +EDGcpfe/21186 of 5/2/19). As a result, every object file that defined the +routine contained a conflicting external definition of it, and linking two +such object files failed. This affected, for example, std::move and +std::forward from libstdc++ 15. Such routines are now marked __weak__. When +GCC is the back end compiler, which gives an error on calls to a weak routine +marked __always_inline__, the __always_inline__ attribute is suppressed +instead. For example, with --g++: + + // twice.h + [[gnu::always_inline]] inline int twice(int x) { return x + x; } + + // test.cpp + #include "twice.h" + using fn = int (*)(int); + fn other(); + int main() { return (&twice == other() && twice(1) == 2) ? 0 : 1; } + + // other.cpp + #include "twice.h" + using fn = int (*)(int); + fn other() { return &twice; } // Linking with test.cpp previously failed. + + 10/5/26 [GH #195] C++-generating back end: dependent multi-dimensional subscripts diff --git a/tests/tests/regressions/gh-175/.test.rto/default.1.1.txt b/tests/tests/regressions/gh-175/.test.rto/default.1.1.txt new file mode 100644 index 0000000000..ca133c56a6 --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.1.1.txt @@ -0,0 +1,3 @@ +eccp -DTEST_NUMBER=1 --c++17 --g++ Test_name.c other.C +Test_name.c: +other.C: diff --git a/tests/tests/regressions/gh-175/.test.rto/default.1.2.txt b/tests/tests/regressions/gh-175/.test.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-175/.test.rto/default.2.1.txt b/tests/tests/regressions/gh-175/.test.rto/default.2.1.txt new file mode 100644 index 0000000000..6d527d102d --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.2.1.txt @@ -0,0 +1,3 @@ +eccp -DTEST_NUMBER=2 --c++17 --g++ -tused Test_name.c other.C +Test_name.c: +other.C: diff --git a/tests/tests/regressions/gh-175/.test.rto/default.2.2.txt b/tests/tests/regressions/gh-175/.test.rto/default.2.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.2.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-175/.test.rto/default.3.1.txt b/tests/tests/regressions/gh-175/.test.rto/default.3.1.txt new file mode 100644 index 0000000000..c276f552aa --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.3.1.txt @@ -0,0 +1,3 @@ +eccp -DTEST_NUMBER=3 --c++17 --clang Test_name.c other.C +Test_name.c: +other.C: diff --git a/tests/tests/regressions/gh-175/.test.rto/default.3.2.txt b/tests/tests/regressions/gh-175/.test.rto/default.3.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/gh-175/.test.rto/default.3.2.txt @@ -0,0 +1 @@ +./a.out