Message ID | ZipGV0B+YovJksu7@tucnak |
---|---|
State | New |
Headers | show |
Series | c++, v4: Retry the aliasing of base/complete cdtor optimization at import_export_decl time [PR113208] | expand |
On Thu, Apr 25, 2024 at 02:02:32PM +0200, Jakub Jelinek wrote: > I've tried the following patch, but unfortunately that lead to large > number of regressions: > +FAIL: g++.dg/cpp0x/initlist25.C -std=c++17 (test for excess errors) So the reduced testcase for this is template <typename T, typename U> struct A { T a1; U a2; template <typename V, typename W, bool = true> constexpr A(V &&x, W &&y) : a1(x), a2(y) {} }; template <typename> struct B; namespace std { template <class> struct initializer_list { int *_M_array; decltype (sizeof 0) _M_len; }; } template <typename T, typename U> struct C { void foo (std::initializer_list<A<const T, U>>); }; template <class> struct D; template <typename T, typename = D<T>, typename = B<T>> struct E { E (const char *); ~E (); }; int main () { C<E<char>, E<char>> m; m.foo ({{"t", "t"}, {"y", "y"}}); } Without the patch I've just posted or even with the earlier version of the patch the _ZN1AIK1EIc1DIcE1BIcEES5_EC[12]IRA2_KcSB_Lb1EEEOT_OT0_ ctors were emitted, but with this patch they are unresolved externals. The reason is that the code actually uses (calls) the _ZN1AIK1EIc1DIcE1BIcEES5_EC1IRA2_KcSB_Lb1EEEOT_OT0_ __ct_comp constructor, that one has TREE_USED, while the _ZN1AIK1EIc1DIcE1BIcEES5_EC2IRA2_KcSB_Lb1EEEOT_OT0_ __ct_base constructor is not TREE_USED. But the c_parse_final_cleanups loop over FOR_EACH_VEC_SAFE_ELT (deferred_fns, i, decl) will ignore the TREE_USED __ct_comp because it is an alias and so has !DECL_SAVED_TREE: 5273 if (!DECL_SAVED_TREE (decl)) 5274 continue; With the following incremental patch the tests in make check-g++ (haven't tried the coroutine one) which failed with the earlier patch now pass. --- gcc/cp/decl2.cc.jj 2024-04-25 10:52:21.057535959 +0200 +++ gcc/cp/decl2.cc 2024-04-25 16:19:17.385547357 +0200 @@ -5271,7 +5271,19 @@ c_parse_final_cleanups (void) generate_tls_wrapper (decl); if (!DECL_SAVED_TREE (decl)) - continue; + { + cgraph_node *node; + tree tgt; + /* Even when maybe_clone_body created same body alias + has no DECL_SAVED_TREE, if its alias target does, + don't skip it. */ + if (!DECL_CLONED_FUNCTION (decl) + || !(node = cgraph_node::get (decl)) + || !node->cpp_implicit_alias + || !(tgt = node->get_alias_target_tree ()) + || !DECL_SAVED_TREE (tgt)) + continue; + } cgraph_node *node = cgraph_node::get_create (decl); @@ -5299,7 +5311,7 @@ c_parse_final_cleanups (void) node = node->get_alias_target (); node->call_for_symbol_thunks_and_aliases (clear_decl_external, - NULL, true); + NULL, true); /* If we mark !DECL_EXTERNAL one of the symbols in some comdat group, we need to mark all symbols in the same comdat group that way. */ @@ -5309,7 +5321,7 @@ c_parse_final_cleanups (void) next != node; next = dyn_cast<cgraph_node *> (next->same_comdat_group)) next->call_for_symbol_thunks_and_aliases (clear_decl_external, - NULL, true); + NULL, true); } /* If we're going to need to write this function out, and Jakub
On 4/25/24 07:22, Jakub Jelinek wrote: > On Thu, Apr 25, 2024 at 02:02:32PM +0200, Jakub Jelinek wrote: >> I've tried the following patch, but unfortunately that lead to large >> number of regressions: >> +FAIL: g++.dg/cpp0x/initlist25.C -std=c++17 (test for excess errors) > > So the reduced testcase for this is > template <typename T, typename U> struct A { > T a1; > U a2; > template <typename V, typename W, bool = true> > constexpr A(V &&x, W &&y) : a1(x), a2(y) {} > }; > template <typename> struct B; > namespace std { > template <class> struct initializer_list { > int *_M_array; > decltype (sizeof 0) _M_len; > }; > } > template <typename T, typename U> struct C { > void foo (std::initializer_list<A<const T, U>>); > }; > template <class> struct D; > template <typename T, typename = D<T>, typename = B<T>> > struct E { E (const char *); ~E (); }; > int > main () > { > C<E<char>, E<char>> m; > m.foo ({{"t", "t"}, {"y", "y"}}); > } > Without the patch I've just posted or even with the earlier version > of the patch the > _ZN1AIK1EIc1DIcE1BIcEES5_EC[12]IRA2_KcSB_Lb1EEEOT_OT0_ > ctors were emitted, but with this patch they are unresolved externals. > > The reason is that the code actually uses (calls) the > _ZN1AIK1EIc1DIcE1BIcEES5_EC1IRA2_KcSB_Lb1EEEOT_OT0_ > __ct_comp constructor, that one has TREE_USED, while the > _ZN1AIK1EIc1DIcE1BIcEES5_EC2IRA2_KcSB_Lb1EEEOT_OT0_ > __ct_base constructor is not TREE_USED. > > But the c_parse_final_cleanups loop over > FOR_EACH_VEC_SAFE_ELT (deferred_fns, i, decl) > will ignore the TREE_USED __ct_comp because it is an alias > and so has !DECL_SAVED_TREE: > 5273 if (!DECL_SAVED_TREE (decl)) > 5274 continue; Hmm, maybe maybe_clone_body shouldn't clear DECL_SAVED_TREE for aliases, but rather set it to some stub like void_node? Though with all these changes, it's probably better to go with your first patch for GCC 14 and delay this approach to 15. Your v1 patch is OK for 14. Jason
--- gcc/cp/decl2.cc.jj 2024-04-24 18:28:22.299513620 +0200 +++ gcc/cp/decl2.cc 2024-04-25 10:04:18.049476567 +0200 @@ -3312,16 +3312,23 @@ tentative_decl_linkage (tree decl) linkage of all functions, and as that causes writes to the data mapped in from the PCH file, it's advantageous to mark the functions at this point. */ - if (DECL_DECLARED_INLINE_P (decl) - && (!DECL_IMPLICIT_INSTANTIATION (decl) - || DECL_DEFAULTED_FN (decl))) + if (DECL_DECLARED_INLINE_P (decl)) { - /* This function must have external linkage, as - otherwise DECL_INTERFACE_KNOWN would have been - set. */ - gcc_assert (TREE_PUBLIC (decl)); - comdat_linkage (decl); - DECL_INTERFACE_KNOWN (decl) = 1; + if (!DECL_IMPLICIT_INSTANTIATION (decl) + || DECL_DEFAULTED_FN (decl)) + { + /* This function must have external linkage, as + otherwise DECL_INTERFACE_KNOWN would have been + set. */ + gcc_assert (TREE_PUBLIC (decl)); + comdat_linkage (decl); + DECL_INTERFACE_KNOWN (decl) = 1; + } + else if (DECL_MAYBE_IN_CHARGE_CDTOR_P (decl)) + /* For implicit instantiations of cdtors try to make + it comdat, so that maybe_clone_body can use aliases. + See PR113208. */ + maybe_make_one_only (decl); } } else if (VAR_P (decl)) --- gcc/cp/optimize.cc.jj 2024-04-25 09:47:24.936736833 +0200 +++ gcc/cp/optimize.cc 2024-04-25 10:19:25.338332564 +0200 @@ -220,10 +220,8 @@ can_alias_cdtor (tree fn) gcc_assert (DECL_MAYBE_IN_CHARGE_CDTOR_P (fn)); /* Don't use aliases for weak/linkonce definitions unless we can put both symbols in the same COMDAT group. */ - return (DECL_INTERFACE_KNOWN (fn) - && (SUPPORTS_ONE_ONLY || !DECL_WEAK (fn)) - && (!DECL_ONE_ONLY (fn) - || (HAVE_COMDAT_GROUP && DECL_WEAK (fn)))); + return (DECL_WEAK (fn) ? (HAVE_COMDAT_GROUP && DECL_ONE_ONLY (fn)) + : (DECL_INTERFACE_KNOWN (fn) && !DECL_ONE_ONLY (fn))); } /* FN is a [cd]tor, fns is a pointer to an array of length 3. Fill fns --- gcc/cp/decl.cc.jj 2024-04-24 18:28:22.231514532 +0200 +++ gcc/cp/decl.cc 2024-04-25 09:47:06.855991224 +0200 @@ -19254,6 +19254,14 @@ cxx_comdat_group (tree decl) else break; } + /* If a ctor/dtor has already set the comdat group by + maybe_clone_body, don't override it. */ + if (SUPPORTS_ONE_ONLY + && TREE_CODE (decl) == FUNCTION_DECL + && DECL_CLONED_FUNCTION_P (decl) + && SUPPORTS_ONE_ONLY) + if (tree comdat = DECL_COMDAT_GROUP (decl)) + return comdat; } return decl; --- gcc/testsuite/g++.dg/abi/comdat2.C.jj 2024-04-25 09:47:06.855991224 +0200 +++ gcc/testsuite/g++.dg/abi/comdat2.C 2024-04-25 09:47:06.855991224 +0200 @@ -0,0 +1,26 @@ +// PR lto/113208 +// { dg-do compile { target { c++11 && { *-*-*gnu* } } } } +// { dg-additional-options "-O2 -fkeep-inline-functions" } +// { dg-final { scan-assembler "_ZN1BI1CEC5ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC1ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC2ERKS1_,comdat" } } + +template <typename T> +struct A { + int foo () const; + A (int, int); +}; +template <typename T> +struct B : A<T> { + constexpr B (const B &x) : A<T> (1, x.foo ()) {} + B () : A<T> (1, 2) {} +}; +struct C; +struct D : B<C> {}; +void bar (D); + +void +baz (D x) +{ + bar (x); +} --- gcc/testsuite/g++.dg/abi/comdat3.C.jj 2024-04-25 09:47:06.855991224 +0200 +++ gcc/testsuite/g++.dg/abi/comdat3.C 2024-04-25 09:47:06.855991224 +0200 @@ -0,0 +1,22 @@ +// PR lto/113208 +// { dg-do compile { target { c++11 && { *-*-*gnu* } } } } +// { dg-additional-options "-O2" } +// { dg-final { scan-assembler "_ZN1M1SINS_1P1TELN1N1LE2EEC5Ev,comdat" } } +// { dg-final { scan-assembler-not "_ZN1M1SINS_1P1TELN1N1LE2EEC1Ev,comdat" } } +// { dg-final { scan-assembler-not "_ZN1M1SINS_1P1TELN1N1LE2EEC2Ev,comdat" } } + +namespace N { enum L { L1, L2, L3 } const O = L3; } +namespace M { + using N::O; + using N::L; + template <typename, L = O> + struct S { constexpr S () {} }; + namespace P { + struct T; + struct U { S<T> u; }; + void foo () { U (); } + } + extern template class S<P::T>; +} +namespace p = M::P; +template class M::S<p::T>; --- gcc/testsuite/g++.dg/abi/comdat4.C.jj 2024-04-25 10:27:54.141172257 +0200 +++ gcc/testsuite/g++.dg/abi/comdat4.C 2024-04-25 10:28:22.493773334 +0200 @@ -0,0 +1,28 @@ +// PR lto/113208 +// { dg-do compile { target { c++11 && { *-*-*gnu* } } } } +// { dg-additional-options "-O2" } +// { dg-final { scan-assembler "_ZN1BI1CEC5ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC1ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC2ERKS1_,comdat" } } + +template <typename T> +struct A { + int foo () const; + A (int, int); +}; +template <typename T> +struct B : A<T> { + constexpr B (const B &x) : A<T> (1, x.foo ()) {} + B () : A<T> (1, 2) {} +}; +struct C; +struct D : B<C> {}; +void bar (D); + +void +baz (D x) +{ + bar (x); +} + +template struct B<C>; --- gcc/testsuite/g++.dg/abi/comdat5.C.jj 2024-04-25 10:28:35.842585513 +0200 +++ gcc/testsuite/g++.dg/abi/comdat5.C 2024-04-25 10:28:52.413352362 +0200 @@ -0,0 +1,28 @@ +// PR lto/113208 +// { dg-do compile { target { c++11 && { *-*-*gnu* } } } } +// { dg-additional-options "-O2" } +// { dg-final { scan-assembler-not "_ZN1BI1CEC5ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC1ERKS1_,comdat" } } +// { dg-final { scan-assembler-not "_ZN1BI1CEC2ERKS1_,comdat" } } + +template <typename T> +struct A { + int foo () const; + A (int, int); +}; +template <typename T> +struct B : A<T> { + constexpr B (const B &x) : A<T> (1, x.foo ()) {} + B () : A<T> (1, 2) {} +}; +struct C; +struct D : B<C> {}; +void bar (D); + +void +baz (D x) +{ + bar (x); +} + +extern template struct B<C>; --- gcc/testsuite/g++.dg/lto/pr113208_0.C.jj 2024-04-25 09:47:06.856991210 +0200 +++ gcc/testsuite/g++.dg/lto/pr113208_0.C 2024-04-25 09:47:06.855991224 +0200 @@ -0,0 +1,13 @@ +// { dg-lto-do link } +// { dg-lto-options { {-O1 -std=c++20 -flto}} } +// { dg-extra-ld-options "-r -nostdlib -flinker-output=nolto-rel" } +// { dg-require-linker-plugin "" } + +#define CONSTEXPR constexpr +#include "pr113208.h" + +struct QualityValue; +struct k : vector<QualityValue> {}; + +void m(k); +void n(k i) { m(i); } --- gcc/testsuite/g++.dg/lto/pr113208_1.C.jj 2024-04-25 09:47:06.856991210 +0200 +++ gcc/testsuite/g++.dg/lto/pr113208_1.C 2024-04-25 09:47:06.856991210 +0200 @@ -0,0 +1,6 @@ +#define CONSTEXPR +#include "pr113208.h" + +struct QualityValue; +vector<QualityValue> values1; +vector<QualityValue> values{values1}; --- gcc/testsuite/g++.dg/lto/pr113208.h.jj 2024-04-25 09:47:06.856991210 +0200 +++ gcc/testsuite/g++.dg/lto/pr113208.h 2024-04-25 09:47:06.856991210 +0200 @@ -0,0 +1,10 @@ +template <typename _Tp> struct _Vector_base { + int g() const; + _Vector_base(int, int); +}; +template <typename _Tp> +struct vector : _Vector_base<_Tp> { + CONSTEXPR vector(const vector &__x) + : _Vector_base<_Tp>(1, __x.g()) {} + vector() : _Vector_base<_Tp>(1, 2) {} +};