Repository navigation
Fix members of an extern template class from being in every TU [GH #252] #432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10851,12 +10851,19 @@ if this routine has a body (dump nothing if it has no body). | |
| a_boolean is_marked_weak = FALSE; | ||
| a_boolean thread_local_init_case = FALSE; | ||
| a_boolean for_inlining_only; | ||
| a_boolean inline_only_defn = FALSE; | ||
| a_boolean need_gnu_inline_attr = FALSE; | ||
|
|
||
| if (c99_mode && rout->definition_for_inlining_only && | ||
| !gcc_version_is(< 50000)) { | ||
| !gcc_version_is(< 50000) | ||
| #if GNU_EXTENSIONS_ALLOWED | ||
| && !rout->gnu_c89_inline | ||
| #endif /* GNU_EXTENSIONS_ALLOWED */ | ||
| ) { | ||
| /* This is a C99 "inline definition", so we must suppress the "extern" | ||
| keyword to avoid turning it into an external definition. (gcc did | ||
| not support C99 inline semantics until version 5.1.0.) */ | ||
| not support C99 inline semantics until version 5.1.0. A routine with | ||
| the gnu_inline attribute follows the GNU C89 rules instead.) */ | ||
| for_inlining_only = TRUE; | ||
| } else { | ||
| for_inlining_only = FALSE; | ||
|
|
@@ -10881,8 +10888,23 @@ if this routine has a body (dump nothing if it has no body). | |
| provided only for the purpose of inlining -- "extern inline". | ||
| Put out the definition in that case. */ | ||
| has_defn = TRUE; | ||
| inline_only_defn = TRUE; | ||
| if (!for_inlining_only) { | ||
| storage_class = (a_storage_class)sc_extern; | ||
| /* "extern inline" is a definition only for inlining under the GNU | ||
| C89 rules, but an external definition under the C99 rules (the | ||
| gcc default since 5.1), so request the former with the gnu_inline | ||
| attribute. */ | ||
| need_gnu_inline_attr = TRUE; | ||
| #if GCC_IS_GENERATED_CODE_TARGET | ||
| if (gcc_is_generated_code_target && | ||
| gnu_target_version_number < 40200) { | ||
| need_gnu_inline_attr = FALSE; | ||
| } /* if */ | ||
| #endif /* GCC_IS_GENERATED_CODE_TARGET */ | ||
| #if GNU_EXTENSIONS_ALLOWED | ||
| if (rout->gnu_c89_inline) need_gnu_inline_attr = FALSE; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Presumably if this is true the gnu_inline is added outside of this code path; it might be worth adding a comment to clarify why this is happening. Something like: |
||
| #endif /* GNU_EXTENSIONS_ALLOWED */ | ||
| } /* if */ | ||
| } /* if */ | ||
| } /* if */ | ||
|
|
@@ -11143,15 +11165,18 @@ if this routine has a body (dump nothing if it has no body). | |
| #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ | ||
| if (gcc_or_clang_is_generated_code_target && rout->is_inline) { | ||
| /* gcc will be used to compile this generated code, so we know how to | ||
| indicate an inline function. */ | ||
| /* gcc ignores __inline__ on functions with ellipses, so don't | ||
| mark such functions as inline. Recent versions of gcc issue an | ||
| error for a function marked both inline and weak, so don't mark a | ||
| weak function as inline, either. */ | ||
| if (!f_skip_typerefs(rout->type)->variant.routine.extra_info-> | ||
| has_ellipsis && | ||
| indicate an inline function. gcc ignores __inline__ on functions | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing space, should be |
||
| with ellipses, so don't mark such functions as inline. Also, | ||
| recent versions of gcc issue an error for a function marked both | ||
| inline and weak, so don't mark a weak function as inline either */ | ||
| if ((!f_skip_typerefs(rout->type)->variant.routine.extra_info-> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should probably be |
||
| has_ellipsis || | ||
| inline_only_defn) && | ||
| !is_marked_weak) { | ||
| write_tok_str("__inline__ "); | ||
| if (need_gnu_inline_attr) { | ||
| write_tok_str("__attribute__((__gnu_inline__)) "); | ||
| } /* if */ | ||
| } /* if */ | ||
| } /* if */ | ||
| #if GNU_EXTENSIONS_ALLOWED && !LOWER_IFUNC | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| [GH #252] | ||
| C-generating back end: inline-only definitions emitted as external ones | ||
|
|
||
| When an inline function definition was to be used only for inlining (for | ||
| example, a member of a class template specialization that is the subject of | ||
| an explicit instantiation declaration), the C-generating back end emitted it | ||
| as an "extern __inline__" definition for GCC and Clang. That form is a | ||
| definition only for inlining under the GNU C89 rules, but an external | ||
| definition under the C99 rules that GCC uses by default since version 5. | ||
| Every object file that used such a function then contained an external | ||
| definition of it, and linking two such object files failed. For example, | ||
| with --c++17: | ||
|
|
||
| // a.h | ||
| template <class T> struct A { | ||
| int f() { return 1; } | ||
| }; | ||
| extern template struct A<int>; | ||
|
|
||
| // a.cpp | ||
| #include "a.h" | ||
| int g1() { return A<int>().f(); } | ||
|
|
||
| // b.cpp | ||
| #include "a.h" | ||
| int g2() { return A<int>().f(); } // Linking with a.cpp previously failed. | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // c.cpp | ||
| #include "a.h" | ||
| template struct A<int>; | ||
|
|
||
| The same problem affected GNU C89 "extern inline" functions, functions with | ||
| the gnu_inline attribute in C99 and later C modes (which lost the "extern" | ||
| keyword), and such definitions of functions with an ellipsis (which were not | ||
| marked inline). This is now fixed: The C-generating back end now adds the | ||
| __gnu_inline__ attribute to "extern __inline__" definitions, keeps "extern" | ||
| for functions with the gnu_inline attribute, and marks a definition only for | ||
| inlining as inline even when the function has an ellipsis. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1 @@ | ||
| eccp -DTEST_NUMBER=1 --gnu_version=999999 -e 200 --no_wrap --no_wrap_diagnostics --gcc Test_name.c | ||
| eccp: diagnostics generated from compilation of Test_name.int.c: | ||
| Test_name.c: In function 'baz': | ||
| Test_name.c:15:8: error: invalid use of '__builtin_va_arg_pack ()' | ||
| eccp: end of diagnostics from compilation of Test_name.int.c | ||
| eccp: gcc compilation of Test_name.int.c returned an exit status of 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1 @@ | ||
| eccp -DTEST_NUMBER=1 --gnu_version=999999 -e 200 --no_wrap --no_wrap_diagnostics --gcc Test_name.c | ||
| eccp: diagnostics generated from compilation of Test_name.int.c: | ||
| Test_name.c: In function 'baz': | ||
| Test_name.c:15:8: error: invalid use of '__builtin_va_arg_pack ()' | ||
| eccp: end of diagnostics from compilation of Test_name.int.c | ||
| eccp: gcc compilation of Test_name.int.c returned an exit status of 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| eccp -DTEST_NUMBER=1 --c11 Test_name.c use.c def.c | ||
| Test_name.c: | ||
| use.c: | ||
| def.c: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| eccp -DTEST_NUMBER=2 --c11 --gnu=150100 Test_name.c use.c def.c | ||
| Test_name.c: | ||
| use.c: | ||
| def.c: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| eccp -DTEST_NUMBER=3 --c --gnu=40200 Test_name.c use.c def.c | ||
| Test_name.c: | ||
| use.c: | ||
| def.c: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| int h(void) { return 1; } | ||
| int v(int n, ...) { return n; } | ||
| int k(void) { return 1; } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| /* Definitions only for inlining. */ | ||
| #if __GNUC_GNU_INLINE__ | ||
| #define INLINE_ONLY extern inline | ||
| #else | ||
| #define INLINE_ONLY inline | ||
| #endif | ||
| INLINE_ONLY int h(void) { return 1; } | ||
| INLINE_ONLY int v(int n, ...) { return n; } | ||
| #ifdef __GNUC__ | ||
| extern inline __attribute__((gnu_inline)) int k(void) { return 1; } | ||
| #else | ||
| INLINE_ONLY int k(void) { return 1; } | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| //type:rp | ||
| //options:--c11:--c11 --gnu=150100:--c --gnu=40200 | ||
| //source_files:inl.h use.c def.c | ||
| /* h, v, and k are used here and in use.c, but only def.c may define them. | ||
| Each object used to contain an external definition of v, of h (with the | ||
| GNU C89 inline rules), or of k (with the C99 inline rules). */ | ||
| #include "inl.h" | ||
| int use(void); | ||
| int main(void) { | ||
| return (h() == 1 && v(1, 2) == 1 && k() == 1 && use() == 3) ? 0 : 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| #include "inl.h" | ||
| int use(void) { return h() + v(1, 3) + k(); } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ./check_defs -DTEST_NUMBER=1 --c++17 Test_name.c use.C inst.C | ||
| Test_name.c: | ||
| use.C: | ||
| inst.C: | ||
| Test_name.o: | ||
| use.o: | ||
| inst.o: _ZN1AIiE1fEv _ZN1AIiE1gEiz |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ./check_defs -DTEST_NUMBER=2 --c++17 --g++ Test_name.c use.C inst.C | ||
| Test_name.c: | ||
| use.C: | ||
| inst.C: | ||
| Test_name.o: | ||
| use.o: | ||
| inst.o: _ZN1AIiE1fEv _ZN1AIiE1gEiz |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ./check_defs -DTEST_NUMBER=3 --c++17 --g++ -tused Test_name.c use.C inst.C | ||
| Test_name.c: | ||
| use.C: | ||
| inst.C: | ||
| Test_name.o: | ||
| use.o: | ||
| inst.o: _ZN1AIiE1fEv _ZN1AIiE1gEiz |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ./check_defs -DTEST_NUMBER=4 --c++17 --clang Test_name.c use.C inst.C | ||
| Test_name.c: | ||
| use.C: | ||
| inst.C: | ||
| Test_name.o: | ||
| use.o: | ||
| inst.o: _ZN1AIiE1fEv _ZN1AIiE1gEiz |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ./a.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| template <class T> struct A { | ||
| int f() { return 1; } | ||
| int g(int n, ...) { return n; } | ||
| }; | ||
| extern template struct A<int>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| #!/bin/bash | ||
| # Compile each source file to an object file, check that only inst.o (the | ||
| # explicit instantiation) defines A<int>::f and A<int>::g, and then link the | ||
| # object files. | ||
| opts= | ||
| objs= | ||
| for arg in "$@"; do | ||
| case $arg in | ||
| *.c | *.C) objs="$objs ${arg%.*}.o" ;; | ||
| *) opts="$opts $arg" ;; | ||
| esac | ||
| done | ||
| eccp -c "$@" || exit $? | ||
| status=0 | ||
| for obj in $objs; do | ||
| defs=`nm -g --defined-only $obj | grep -o '_ZN1AIiE1[fg]E[a-z]*' | sort` | ||
| defs=`echo $defs` | ||
| echo $obj: $defs | ||
| expected= | ||
| if [ $obj = inst.o ]; then | ||
| expected="_ZN1AIiE1fEv _ZN1AIiE1gEiz" | ||
| fi | ||
| if [ "$defs" != "$expected" ]; then | ||
| echo "FAIL: Expected '$expected'." | ||
| status=1 | ||
| fi | ||
| done | ||
| if [ $status -ne 0 ]; then | ||
| exit $status | ||
| fi | ||
| exec eccp $opts $objs |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| #include "a.h" | ||
| template struct A<int>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| //type:rp | ||
| //options:--c++17:--c++17 --g++:--c++17 --g++ -tused:--c++17 --clang | ||
| //source_files:a.h use.C inst.C | ||
| //script:./check_defs | ||
| // The members of A<int> are used here and in use.C, but only inst.C may | ||
| // define them; each object used to contain an external definition. | ||
| #include "a.h" | ||
| int use(); | ||
| int main() { | ||
| return (A<int>().f() == 1 && A<int>().g(1, 2) == 1 && use() == 2) ? 0 : 1; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| #include "a.h" | ||
| int use() { return A<int>().f() + A<int>().g(1, 3); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above comment mentions gcc 5.1, but this tests for 4.2; it would be good to clarify what's going on (or make the test relative to 5.1).